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>
This commit is contained in:
co-authored by
Ryan McKinley
parent
0b822478b6
commit
4baca6947d
@@ -9,15 +9,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/protobuf/proto"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const trace_prefix = "sql.resource."
|
||||
@@ -314,7 +315,10 @@ func (b *backend) Read(ctx context.Context, req *resource.ReadRequest) (*resourc
|
||||
|
||||
res, err := dbutil.QueryRow(ctx, b.db, sr, readReq)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, resource.ErrNotFound
|
||||
return nil, apierrors.NewNotFound(schema.GroupResource{
|
||||
Group: req.Key.Group,
|
||||
Resource: req.Key.Resource,
|
||||
}, req.Key.Name)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("get resource version: %w", err)
|
||||
}
|
||||
@@ -540,7 +544,7 @@ func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialec
|
||||
resourceVersion: new(resourceVersion),
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, fmt.Errorf("now row for the provided resource version")
|
||||
return 1, nil
|
||||
} else if err != nil {
|
||||
return 0, fmt.Errorf("get resource version: %w", err)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -7,7 +7,11 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/grafana/dskit/services"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
infraDB "github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -279,6 +283,75 @@ func TestBackendPrepareList(t *testing.T) {
|
||||
assert.Equal(t, int64(4), continueToken.StartOffset)
|
||||
})
|
||||
}
|
||||
func TestClientServer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dbstore := infraDB.InitTestDB(t)
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.GRPCServerAddress = "localhost:0"
|
||||
cfg.GRPCServerNetwork = "tcp"
|
||||
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorage)
|
||||
|
||||
svc, err := sql.ProvideService(cfg, features, dbstore, nil)
|
||||
require.NoError(t, err)
|
||||
var client resource.ResourceStoreClient
|
||||
|
||||
// Test with an admin identity
|
||||
clientCtx := identity.WithRequester(context.Background(), &identity.StaticRequester{
|
||||
Type: identity.TypeUser,
|
||||
Login: "testuser",
|
||||
UserID: 123,
|
||||
UserUID: "u123",
|
||||
OrgRole: identity.RoleAdmin,
|
||||
IsGrafanaAdmin: true, // can do anything
|
||||
})
|
||||
|
||||
t.Run("Start and stop service", func(t *testing.T) {
|
||||
err = services.StartAndAwaitRunning(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, svc.GetAddress())
|
||||
})
|
||||
|
||||
t.Run("Create a client", func(t *testing.T) {
|
||||
conn, err := grpc.NewClient(svc.GetAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
require.NoError(t, err)
|
||||
client = resource.NewResourceStoreClientGRPC(conn)
|
||||
})
|
||||
|
||||
t.Run("Create a resource", func(t *testing.T) {
|
||||
raw := []byte(`{
|
||||
"apiVersion": "group/v0alpha1",
|
||||
"kind": "resource",
|
||||
"metadata": {
|
||||
"name": "item1",
|
||||
"namespace": "namespace"
|
||||
},
|
||||
"spec": {}
|
||||
}`)
|
||||
resp, err := client.Create(clientCtx, &resource.CreateRequest{
|
||||
Key: resourceKey("item1"),
|
||||
Value: raw,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, resp.Error)
|
||||
require.Greater(t, resp.ResourceVersion, int64(0))
|
||||
})
|
||||
|
||||
t.Run("Read the resource", func(t *testing.T) {
|
||||
resp, err := client.Read(clientCtx, &resource.ReadRequest{
|
||||
Key: resourceKey("item1"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, resp.Error)
|
||||
require.Greater(t, resp.ResourceVersion, int64(0))
|
||||
})
|
||||
|
||||
t.Run("Stop the service", func(t *testing.T) {
|
||||
err = services.StopAndAwaitTerminated(ctx, svc)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func writeEvent(ctx context.Context, store sql.Backend, name string, action resource.WatchEvent_Type) (int64, error) {
|
||||
return store.WriteEvent(ctx, resource.WriteEvent{
|
||||
|
||||
Reference in New Issue
Block a user