mirror of https://github.com/grafana/grafana
ResourceServer: make the resource store the default unified storage backend (#90899)
* make the resource store the default unified storage backend * add integration tests * fix test non passing * Update pkg/storage/unified/sql/test/integration_test.go Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * lint * fix tests * fix no rows --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/90979/head
parent
0b822478b6
commit
4baca6947d
@ -0,0 +1,88 @@ |
|||||||
|
package resource |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"time" |
||||||
|
|
||||||
|
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" |
||||||
|
"google.golang.org/grpc/health/grpc_health_v1" |
||||||
|
) |
||||||
|
|
||||||
|
// Compile-time assertion
|
||||||
|
var _ HealthService = &healthServer{} |
||||||
|
|
||||||
|
type HealthService interface { |
||||||
|
grpc_health_v1.HealthServer |
||||||
|
grpcAuth.ServiceAuthFuncOverride |
||||||
|
} |
||||||
|
|
||||||
|
func ProvideHealthService(server DiagnosticsServer) (grpc_health_v1.HealthServer, error) { |
||||||
|
h := &healthServer{srv: server} |
||||||
|
return h, nil |
||||||
|
} |
||||||
|
|
||||||
|
type healthServer struct { |
||||||
|
srv DiagnosticsServer |
||||||
|
} |
||||||
|
|
||||||
|
// AuthFuncOverride for no auth for health service.
|
||||||
|
func (s *healthServer) AuthFuncOverride(ctx context.Context, _ string) (context.Context, error) { |
||||||
|
return ctx, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *healthServer) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { |
||||||
|
r, err := s.srv.IsHealthy(ctx, &HealthCheckRequest{}) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &grpc_health_v1.HealthCheckResponse{ |
||||||
|
Status: grpc_health_v1.HealthCheckResponse_ServingStatus(r.Status.Number()), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *healthServer) Watch(req *grpc_health_v1.HealthCheckRequest, stream grpc_health_v1.Health_WatchServer) error { |
||||||
|
h, err := s.srv.IsHealthy(stream.Context(), &HealthCheckRequest{}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// send initial health status
|
||||||
|
err = stream.Send(&grpc_health_v1.HealthCheckResponse{ |
||||||
|
Status: grpc_health_v1.HealthCheckResponse_ServingStatus(h.Status.Number()), |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
currHealth := h.Status.Number() |
||||||
|
ticker := time.NewTicker(5 * time.Second) |
||||||
|
defer ticker.Stop() |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-ticker.C: |
||||||
|
// get current health status
|
||||||
|
h, err := s.srv.IsHealthy(stream.Context(), &HealthCheckRequest{}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// if health status has not changed, continue
|
||||||
|
if h.Status.Number() == currHealth { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// send the new health status
|
||||||
|
currHealth = h.Status.Number() |
||||||
|
err = stream.Send(&grpc_health_v1.HealthCheckResponse{ |
||||||
|
Status: grpc_health_v1.HealthCheckResponse_ServingStatus(h.Status.Number()), |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
case <-stream.Context().Done(): |
||||||
|
return errors.New("stream closed, context cancelled") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,123 @@ |
|||||||
|
package resource |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
sync "sync" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/health/grpc_health_v1" |
||||||
|
) |
||||||
|
|
||||||
|
func TestHealthCheck(t *testing.T) { |
||||||
|
t.Run("will return serving response when healthy", func(t *testing.T) { |
||||||
|
stub := &diag{healthResponse: HealthCheckResponse_SERVING} |
||||||
|
svc, err := ProvideHealthService(stub) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
req := &grpc_health_v1.HealthCheckRequest{} |
||||||
|
res, err := svc.Check(context.Background(), req) |
||||||
|
|
||||||
|
require.NoError(t, err) |
||||||
|
assert.Equal(t, grpc_health_v1.HealthCheckResponse_SERVING, res.Status) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("will return not serving response when not healthy", func(t *testing.T) { |
||||||
|
stub := &diag{healthResponse: HealthCheckResponse_NOT_SERVING} |
||||||
|
svc, err := ProvideHealthService(stub) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
req := &grpc_health_v1.HealthCheckRequest{} |
||||||
|
res, err := svc.Check(context.Background(), req) |
||||||
|
|
||||||
|
require.NoError(t, err) |
||||||
|
assert.Equal(t, grpc_health_v1.HealthCheckResponse_NOT_SERVING, res.Status) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestHealthWatch(t *testing.T) { |
||||||
|
t.Run("watch will return message when called", func(t *testing.T) { |
||||||
|
stub := &diag{healthResponse: HealthCheckResponse_SERVING} |
||||||
|
svc, err := ProvideHealthService(stub) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
req := &grpc_health_v1.HealthCheckRequest{} |
||||||
|
stream := &fakeHealthWatchServer{} |
||||||
|
go func() { |
||||||
|
err := svc.Watch(req, stream) |
||||||
|
require.NoError(t, err) |
||||||
|
}() |
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond) |
||||||
|
err = stream.RecvMsg(nil) |
||||||
|
require.NoError(t, err) |
||||||
|
}) |
||||||
|
|
||||||
|
t.Run("watch will return error when context cancelled", func(t *testing.T) { |
||||||
|
stub := &diag{healthResponse: HealthCheckResponse_NOT_SERVING} |
||||||
|
svc, err := ProvideHealthService(stub) |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
req := &grpc_health_v1.HealthCheckRequest{} |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) |
||||||
|
defer cancel() |
||||||
|
stream := &fakeHealthWatchServer{context: ctx} |
||||||
|
err = svc.Watch(req, stream) |
||||||
|
|
||||||
|
require.Error(t, err) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
var _ DiagnosticsServer = &diag{} |
||||||
|
|
||||||
|
type diag struct { |
||||||
|
healthResponse HealthCheckResponse_ServingStatus |
||||||
|
error error |
||||||
|
} |
||||||
|
|
||||||
|
func (s *diag) IsHealthy(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) { |
||||||
|
if s.error != nil { |
||||||
|
return nil, s.error |
||||||
|
} |
||||||
|
|
||||||
|
return &HealthCheckResponse{Status: s.healthResponse}, nil |
||||||
|
} |
||||||
|
|
||||||
|
type fakeHealthWatchServer struct { |
||||||
|
mu sync.Mutex |
||||||
|
grpc.ServerStream |
||||||
|
healthChecks []*grpc_health_v1.HealthCheckResponse |
||||||
|
context context.Context |
||||||
|
} |
||||||
|
|
||||||
|
func (f *fakeHealthWatchServer) Send(resp *grpc_health_v1.HealthCheckResponse) error { |
||||||
|
f.mu.Lock() |
||||||
|
defer f.mu.Unlock() |
||||||
|
f.healthChecks = append(f.healthChecks, resp) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *fakeHealthWatchServer) RecvMsg(m interface{}) error { |
||||||
|
f.mu.Lock() |
||||||
|
defer f.mu.Unlock() |
||||||
|
if len(f.healthChecks) == 0 { |
||||||
|
return errors.New("no health checks received") |
||||||
|
} |
||||||
|
f.healthChecks = f.healthChecks[1:] |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *fakeHealthWatchServer) SendMsg(m interface{}) error { |
||||||
|
return errors.New("not implemented") |
||||||
|
} |
||||||
|
|
||||||
|
func (f *fakeHealthWatchServer) Context() context.Context { |
||||||
|
if f.context == nil { |
||||||
|
f.context = context.Background() |
||||||
|
} |
||||||
|
return f.context |
||||||
|
} |
||||||
@ -0,0 +1,134 @@ |
|||||||
|
package sql |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"github.com/grafana/dskit/services" |
||||||
|
"github.com/prometheus/client_golang/prometheus" |
||||||
|
"google.golang.org/grpc/health/grpc_health_v1" |
||||||
|
|
||||||
|
infraDB "github.com/grafana/grafana/pkg/infra/db" |
||||||
|
"github.com/grafana/grafana/pkg/infra/log" |
||||||
|
"github.com/grafana/grafana/pkg/infra/tracing" |
||||||
|
"github.com/grafana/grafana/pkg/modules" |
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||||
|
"github.com/grafana/grafana/pkg/services/grpcserver" |
||||||
|
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors" |
||||||
|
"github.com/grafana/grafana/pkg/setting" |
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource" |
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
_ Service = (*service)(nil) |
||||||
|
) |
||||||
|
|
||||||
|
type Service interface { |
||||||
|
services.NamedService |
||||||
|
} |
||||||
|
|
||||||
|
type service struct { |
||||||
|
*services.BasicService |
||||||
|
|
||||||
|
cfg *setting.Cfg |
||||||
|
features featuremgmt.FeatureToggles |
||||||
|
db infraDB.DB |
||||||
|
stopCh chan struct{} |
||||||
|
stoppedCh chan error |
||||||
|
|
||||||
|
handler grpcserver.Provider |
||||||
|
|
||||||
|
tracing *tracing.TracingService |
||||||
|
|
||||||
|
authenticator interceptors.Authenticator |
||||||
|
|
||||||
|
log log.Logger |
||||||
|
} |
||||||
|
|
||||||
|
func ProvideService( |
||||||
|
cfg *setting.Cfg, |
||||||
|
features featuremgmt.FeatureToggles, |
||||||
|
db infraDB.DB, |
||||||
|
log log.Logger, |
||||||
|
) (*service, error) { |
||||||
|
tracingCfg, err := tracing.ProvideTracingConfig(cfg) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
tracingCfg.ServiceName = "unified-storage" |
||||||
|
|
||||||
|
tracing, err := tracing.ProvideService(tracingCfg) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
authn := &grpc.Authenticator{} |
||||||
|
|
||||||
|
s := &service{ |
||||||
|
cfg: cfg, |
||||||
|
features: features, |
||||||
|
stopCh: make(chan struct{}), |
||||||
|
authenticator: authn, |
||||||
|
tracing: tracing, |
||||||
|
db: db, |
||||||
|
log: log, |
||||||
|
} |
||||||
|
|
||||||
|
// This will be used when running as a dskit service
|
||||||
|
s.BasicService = services.NewBasicService(s.start, s.running, nil).WithName(modules.StorageServer) |
||||||
|
|
||||||
|
return s, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *service) start(ctx context.Context) error { |
||||||
|
server, err := ProvideResourceServer(s.db, s.cfg, s.features, s.tracing) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
s.handler, err = grpcserver.ProvideService(s.cfg, s.features, s.authenticator, s.tracing, prometheus.DefaultRegisterer) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
healthService, err := resource.ProvideHealthService(server) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
resource.RegisterResourceStoreServer(s.handler.GetServer(), server) |
||||||
|
grpc_health_v1.RegisterHealthServer(s.handler.GetServer(), healthService) |
||||||
|
|
||||||
|
// register reflection service
|
||||||
|
_, err = grpcserver.ProvideReflectionService(s.cfg, s.handler) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// start the gRPC server
|
||||||
|
go func() { |
||||||
|
err := s.handler.Run(ctx) |
||||||
|
if err != nil { |
||||||
|
s.stoppedCh <- err |
||||||
|
} else { |
||||||
|
s.stoppedCh <- nil |
||||||
|
} |
||||||
|
}() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// GetAddress returns the address of the gRPC server.
|
||||||
|
func (s *service) GetAddress() string { |
||||||
|
return s.handler.GetAddress() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *service) running(ctx context.Context) error { |
||||||
|
select { |
||||||
|
case err := <-s.stoppedCh: |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
case <-ctx.Done(): |
||||||
|
close(s.stopCh) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
Loading…
Reference in new issue