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