Zanzana: Use cache for both streamed and non-stream version of list objects (#98882)
* Add prefix constants and use string builders / string concatinations * Use cache for both streamed and non-stream versions of list objects * Remove unused constants
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -28,6 +28,12 @@ const (
|
||||
TypeGroupResouce string = "group_resource"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeFolderPrefix string = TypeFolder + ":"
|
||||
TypeResourcePrefix string = TypeResource + ":"
|
||||
TypeGroupResoucePrefix string = TypeGroupResouce + ":"
|
||||
)
|
||||
|
||||
const (
|
||||
RelationTeamMember string = "member"
|
||||
RelationTeamAdmin string = "admin"
|
||||
@@ -140,31 +146,37 @@ func isValidRelation(relation string, valid []string) bool {
|
||||
}
|
||||
|
||||
func FolderResourceRelation(relation string) string {
|
||||
return fmt.Sprintf("%s_%s", TypeResource, relation)
|
||||
return TypeResource + "_" + relation
|
||||
}
|
||||
|
||||
func NewTypedIdent(typ string, name string) string {
|
||||
return fmt.Sprintf("%s:%s", typ, name)
|
||||
return typ + ":" + name
|
||||
}
|
||||
|
||||
func NewResourceIdent(group, resource, subresource, name string) string {
|
||||
return fmt.Sprintf("%s:%s/%s", TypeResource, FormatGroupResource(group, resource, subresource), name)
|
||||
return TypeResourcePrefix + FormatGroupResource(group, resource, subresource) + "/" + name
|
||||
}
|
||||
|
||||
func NewFolderIdent(name string) string {
|
||||
return fmt.Sprintf("%s:%s", TypeFolder, name)
|
||||
return TypeFolderPrefix + name
|
||||
}
|
||||
|
||||
func NewGroupResourceIdent(group, resource, subresource string) string {
|
||||
return fmt.Sprintf("%s:%s", TypeGroupResouce, FormatGroupResource(group, resource, subresource))
|
||||
return TypeGroupResoucePrefix + FormatGroupResource(group, resource, subresource)
|
||||
}
|
||||
|
||||
func FormatGroupResource(group, resource, subresource string) string {
|
||||
b := strings.Builder{}
|
||||
b.WriteString(group)
|
||||
b.WriteRune('/')
|
||||
b.WriteString(resource)
|
||||
|
||||
if subresource != "" {
|
||||
return fmt.Sprintf("%s/%s/%s", group, resource, subresource)
|
||||
b.WriteRune('/')
|
||||
b.WriteString(subresource)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", group, resource)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func NewResourceTuple(subject, relation, group, resource, subresource, name string) *openfgav1.TupleKey {
|
||||
|
||||
@@ -20,13 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceType = "resource"
|
||||
namespaceType = "namespace"
|
||||
folderTypePrefix = "folder:"
|
||||
|
||||
cacheCleanInterval = 2 * time.Minute
|
||||
)
|
||||
const cacheCleanInterval = 2 * time.Minute
|
||||
|
||||
var _ authzv1.AuthzServiceServer = (*Server)(nil)
|
||||
var _ authzextv1.AuthzExtentionServiceServer = (*Server)(nil)
|
||||
|
||||
@@ -2,7 +2,10 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||
@@ -48,13 +51,6 @@ func (s *Server) List(ctx context.Context, r *authzv1.ListRequest) (*authzv1.Lis
|
||||
return s.listTyped(ctx, r.GetSubject(), relation, resource, contextuals, store)
|
||||
}
|
||||
|
||||
func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
if s.cfg.UseStreamedListObjects {
|
||||
return s.streamedListObjects(ctx, req)
|
||||
}
|
||||
return s.openfga.ListObjects(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) listTyped(ctx context.Context, subject, relation string, resource common.ResourceInfo, contextuals *openfgav1.ContextualTupleKeys, store *storeInfo) (*authzv1.ListResponse, error) {
|
||||
if !resource.IsValidRelation(relation) {
|
||||
return &authzv1.ListResponse{}, nil
|
||||
@@ -125,20 +121,103 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation string, reso
|
||||
|
||||
return &authzv1.ListResponse{
|
||||
Folders: folderObject(folders),
|
||||
Items: directObjects(resource.GroupResource(), objects),
|
||||
Items: genericObjects(resource.GroupResource(), objects),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
fn := s.openfga.ListObjects
|
||||
if s.cfg.UseStreamedListObjects {
|
||||
fn = s.streamedListObjects
|
||||
}
|
||||
|
||||
if s.cfg.CheckQueryCache {
|
||||
return s.listObjectCached(ctx, req, fn)
|
||||
}
|
||||
|
||||
return fn(ctx, req)
|
||||
}
|
||||
|
||||
type listFn func(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error)
|
||||
|
||||
func (s *Server) listObjectCached(ctx context.Context, req *openfgav1.ListObjectsRequest, fn listFn) (*openfgav1.ListObjectsResponse, error) {
|
||||
ctx, span := tracer.Start(ctx, "server.listObjectCached")
|
||||
defer span.End()
|
||||
|
||||
key, err := getRequestHash(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res, ok := s.cache.Get(key); ok {
|
||||
return res.(*openfgav1.ListObjectsResponse), nil
|
||||
}
|
||||
|
||||
res, err := fn(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.cache.Set(key, res, 0)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Server) streamedListObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
ctx, span := tracer.Start(ctx, "server.streamedListObjects")
|
||||
defer span.End()
|
||||
|
||||
r := &openfgav1.StreamedListObjectsRequest{
|
||||
StoreId: req.GetStoreId(),
|
||||
AuthorizationModelId: req.GetAuthorizationModelId(),
|
||||
Type: req.GetType(),
|
||||
Relation: req.GetRelation(),
|
||||
User: req.GetUser(),
|
||||
Context: req.GetContext(),
|
||||
ContextualTuples: req.ContextualTuples,
|
||||
}
|
||||
|
||||
stream, err := s.openfgaClient.StreamedListObjects(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var objects []string
|
||||
for {
|
||||
res, err := stream.Recv()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, res.GetObject())
|
||||
}
|
||||
|
||||
return &openfgav1.ListObjectsResponse{
|
||||
Objects: objects,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getRequestHash(req *openfgav1.ListObjectsRequest) (string, error) {
|
||||
hash := fnv.New64a()
|
||||
_, err := hash.Write([]byte(req.String()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func typedObjects(typ string, objects []string) []string {
|
||||
prefix := fmt.Sprintf("%s:", typ)
|
||||
prefix := typ + ":"
|
||||
for i := range objects {
|
||||
objects[i] = strings.TrimPrefix(objects[i], prefix)
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
func directObjects(gr string, objects []string) []string {
|
||||
prefix := fmt.Sprintf("%s:%s/", resourceType, gr)
|
||||
func genericObjects(gr string, objects []string) []string {
|
||||
prefix := common.TypeResourcePrefix + gr + "/"
|
||||
for i := range objects {
|
||||
objects[i] = strings.TrimPrefix(objects[i], prefix)
|
||||
}
|
||||
@@ -147,7 +226,7 @@ func directObjects(gr string, objects []string) []string {
|
||||
|
||||
func folderObject(objects []string) []string {
|
||||
for i := range objects {
|
||||
objects[i] = strings.TrimPrefix(objects[i], folderTypePrefix)
|
||||
objects[i] = strings.TrimPrefix(objects[i], common.TypeFolderPrefix)
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
func (s *Server) streamedListObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
if !s.cfg.CheckQueryCache {
|
||||
return s.listObjectsWithStream(ctx, req)
|
||||
}
|
||||
return s.listObjectsWithStreamCached(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) listObjectsWithStreamCached(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
ctx, span := tracer.Start(ctx, "server.listObjectsWithStreamCached")
|
||||
defer span.End()
|
||||
|
||||
reqHash, err := getRequestHash(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res, ok := s.cache.Get(reqHash); ok {
|
||||
return res.(*openfgav1.ListObjectsResponse), nil
|
||||
}
|
||||
|
||||
res, err := s.listObjectsWithStream(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.cache.Set(reqHash, res, 0)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Server) listObjectsWithStream(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
|
||||
ctx, span := tracer.Start(ctx, "server.listObjectsWithStream")
|
||||
defer span.End()
|
||||
|
||||
r := &openfgav1.StreamedListObjectsRequest{
|
||||
StoreId: req.GetStoreId(),
|
||||
AuthorizationModelId: req.GetAuthorizationModelId(),
|
||||
Type: req.GetType(),
|
||||
Relation: req.GetRelation(),
|
||||
User: req.GetUser(),
|
||||
Context: req.GetContext(),
|
||||
ContextualTuples: req.ContextualTuples,
|
||||
}
|
||||
|
||||
stream, err := s.openfgaClient.StreamedListObjects(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var objects []string
|
||||
for {
|
||||
res, err := stream.Recv()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, res.GetObject())
|
||||
}
|
||||
|
||||
return &openfgav1.ListObjectsResponse{
|
||||
Objects: objects,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getRequestHash(req *openfgav1.ListObjectsRequest) (string, error) {
|
||||
hash := fnv.New64a()
|
||||
_, err := hash.Write([]byte(req.String()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user