From c9df9f5f31de1bee52166089aa1d6a8daed30245 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Tue, 7 Nov 2023 16:40:11 +0100 Subject: [PATCH] graph: Make user and group lookup cache re-usable drives.go implemented a local user/group cache (ttl based) to speed up repeated user and group lookups. This commit moves the implementation to the 'identity' module to make it usable outside of drives.go. --- services/graph/pkg/identity/cache.go | 139 +++++++++++++++++++++++ services/graph/pkg/service/v0/drives.go | 25 +--- services/graph/pkg/service/v0/graph.go | 4 +- services/graph/pkg/service/v0/service.go | 22 +--- 4 files changed, 151 insertions(+), 39 deletions(-) create mode 100644 services/graph/pkg/identity/cache.go diff --git a/services/graph/pkg/identity/cache.go b/services/graph/pkg/identity/cache.go new file mode 100644 index 0000000000..a0bdf65d86 --- /dev/null +++ b/services/graph/pkg/identity/cache.go @@ -0,0 +1,139 @@ +package identity + +import ( + "context" + "time" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + cs3Group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + cs3User "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + revautils "github.com/cs3org/reva/v2/pkg/utils" + "github.com/jellydator/ttlcache/v3" + libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" +) + +// IdentityCache implements a simple ttl based cache for looking up users and groups by ID +type IdentityCache struct { + users *ttlcache.Cache[string, libregraph.User] + groups *ttlcache.Cache[string, libregraph.Group] + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] +} + +type identityCacheOptions struct { + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + usersTTL time.Duration + groupsTTL time.Duration +} + +// IdentityCacheOptiondefines a single option function. +type IdentityCacheOption func(o *identityCacheOptions) + +// IdentityCacheWithGatewaySelector set the gatewaySelector for the Identity Cache +func IdentityCacheWithGatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) IdentityCacheOption { + return func(o *identityCacheOptions) { + o.gatewaySelector = gatewaySelector + } +} + +// IdentityCacheWithUsersTTL sets the TTL for the users cache +func IdentityCacheWithUsersTTL(ttl time.Duration) IdentityCacheOption { + return func(o *identityCacheOptions) { + o.usersTTL = ttl + } +} + +// IdentityCacheWithGroupsTTL sets the TTL for the groups cache +func IdentityCacheWithGroupsTTL(ttl time.Duration) IdentityCacheOption { + return func(o *identityCacheOptions) { + o.groupsTTL = ttl + } +} + +func newOptions(opts ...IdentityCacheOption) identityCacheOptions { + opt := identityCacheOptions{} + for _, o := range opts { + o(&opt) + } + return opt +} + +// NewIdentityCache instanciates a new IdentityCache and sets the supplied options +func NewIdentityCache(opts ...IdentityCacheOption) IdentityCache { + opt := newOptions(opts...) + + var cache IdentityCache + + cache.users = ttlcache.New( + ttlcache.WithTTL[string, libregraph.User](opt.usersTTL), + ttlcache.WithDisableTouchOnHit[string, libregraph.User](), + ) + go cache.users.Start() + + cache.groups = ttlcache.New( + ttlcache.WithTTL[string, libregraph.Group](opt.groupsTTL), + ttlcache.WithDisableTouchOnHit[string, libregraph.Group](), + ) + go cache.groups.Start() + + cache.gatewaySelector = opt.gatewaySelector + + return cache +} + +// GetUser looks up a user by id, if the user is not cached yet it will do a lookup via the CS3 API +func (cache IdentityCache) GetUser(ctx context.Context, userid string) (libregraph.User, error) { + var user libregraph.User + if item := cache.users.Get(userid); item == nil { + gatewayClient, err := cache.gatewaySelector.Next() + cs3UserID := &cs3User.UserId{ + OpaqueId: userid, + } + cs3User, err := revautils.GetUser(cs3UserID, gatewayClient) + if err != nil { + if revautils.IsErrNotFound(err) { + return libregraph.User{}, ErrNotFound + } + return libregraph.User{}, errorcode.New(errorcode.GeneralException, err.Error()) + } + user = *CreateUserModelFromCS3(cs3User) + cache.users.Set(userid, user, ttlcache.DefaultTTL) + + } else { + user = item.Value() + } + return user, nil +} + +// GetUser looks up a group by id, if the group is not cached yet it will do a lookup via the CS3 API +func (cache IdentityCache) GetGroup(ctx context.Context, groupid string) (libregraph.Group, error) { + var group libregraph.Group + if item := cache.groups.Get(groupid); item == nil { + gatewayClient, err := cache.gatewaySelector.Next() + cs3GroupID := &cs3Group.GroupId{ + OpaqueId: groupid, + } + req := cs3Group.GetGroupRequest{ + GroupId: cs3GroupID, + } + res, err := gatewayClient.GetGroup(ctx, &req) + if err != nil { + return group, errorcode.New(errorcode.GeneralException, err.Error()) + } + switch res.Status.Code { + case rpc.Code_CODE_OK: + cs3Group := res.GetGroup() + group = *CreateGroupModelFromCS3(cs3Group) + cache.groups.Set(groupid, group, ttlcache.DefaultTTL) + case rpc.Code_CODE_NOT_FOUND: + return group, ErrNotFound + default: + return group, errorcode.New(errorcode.GeneralException, res.Status.Message) + } + } else { + group = item.Value() + } + return group, nil +} diff --git a/services/graph/pkg/service/v0/drives.go b/services/graph/pkg/service/v0/drives.go index f3f1d5da35..fe4c42b923 100644 --- a/services/graph/pkg/service/v0/drives.go +++ b/services/graph/pkg/service/v0/drives.go @@ -23,7 +23,6 @@ import ( "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-chi/render" - "github.com/jellydator/ttlcache/v3" libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/service/grpc" v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" @@ -780,28 +779,16 @@ func (g Graph) cs3PermissionsToLibreGraph(ctx context.Context, space *storagepro tmp := id var identitySet libregraph.IdentitySet if _, ok := groupsMap[id]; ok { - var group libregraph.Group - if item := g.groupsCache.Get(id); item == nil { - if requestedGroup, err := g.identityBackend.GetGroup(ctx, id, url.Values{}); err == nil { - group = *requestedGroup - g.groupsCache.Set(id, group, ttlcache.DefaultTTL) - } - } else { - group = item.Value() + group, err := g.identityCache.GetGroup(ctx, tmp) + if err != nil { + g.logger.Warn().Str("groupid", tmp).Msg("Group not found by id") } - identitySet = libregraph.IdentitySet{Group: &libregraph.Identity{Id: &tmp, DisplayName: group.GetDisplayName()}} } else { - var user libregraph.User - if item := g.usersCache.Get(id); item == nil { - if requestedUser, err := g.identityBackend.GetUser(ctx, id, &godata.GoDataRequest{}); err == nil { - user = *requestedUser - g.usersCache.Set(id, user, ttlcache.DefaultTTL) - } - } else { - user = item.Value() + user, err := g.identityCache.GetUser(ctx, tmp) + if err != nil { + g.logger.Warn().Str("userid", tmp).Msg("User not found by id") } - identitySet = libregraph.IdentitySet{User: &libregraph.Identity{Id: &tmp, DisplayName: user.GetDisplayName()}} } diff --git a/services/graph/pkg/service/v0/graph.go b/services/graph/pkg/service/v0/graph.go index 29d22becca..4ade5d0708 100644 --- a/services/graph/pkg/service/v0/graph.go +++ b/services/graph/pkg/service/v0/graph.go @@ -15,7 +15,6 @@ import ( "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/go-chi/chi/v5" "github.com/jellydator/ttlcache/v3" - libregraph "github.com/owncloud/libre-graph-api-go" "github.com/owncloud/ocis/v2/ocis-pkg/keycloak" "github.com/owncloud/ocis/v2/ocis-pkg/log" ehsvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/eventhistory/v0" @@ -71,8 +70,7 @@ type Graph struct { roleService RoleService permissionsService Permissions specialDriveItemsCache *ttlcache.Cache[string, interface{}] - usersCache *ttlcache.Cache[string, libregraph.User] - groupsCache *ttlcache.Cache[string, libregraph.Group] + identityCache identity.IdentityCache eventsPublisher events.Publisher searchService searchsvc.SearchProviderService keycloakClient keycloak.Client diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index b00f55834e..665d6f0dc4 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -16,7 +16,6 @@ import ( "github.com/go-chi/chi/v5/middleware" ldapv3 "github.com/go-ldap/ldap/v3" "github.com/jellydator/ttlcache/v3" - libregraph "github.com/owncloud/libre-graph-api-go" ocisldap "github.com/owncloud/ocis/v2/ocis-pkg/ldap" "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/ocis-pkg/roles" @@ -125,29 +124,18 @@ func NewService(opts ...Option) (Graph, error) { ) go spacePropertiesCache.Start() - usersCache := ttlcache.New( - ttlcache.WithTTL[string, libregraph.User]( - time.Duration(options.Config.Spaces.UsersCacheTTL), - ), - ttlcache.WithDisableTouchOnHit[string, libregraph.User](), + identityCache := identity.NewIdentityCache( + identity.IdentityCacheWithGatewaySelector(options.GatewaySelector), + identity.IdentityCacheWithUsersTTL(time.Duration(options.Config.Spaces.UsersCacheTTL)), + identity.IdentityCacheWithGroupsTTL(time.Duration(options.Config.Spaces.GroupsCacheTTL)), ) - go usersCache.Start() - - groupsCache := ttlcache.New( - ttlcache.WithTTL[string, libregraph.Group]( - time.Duration(options.Config.Spaces.GroupsCacheTTL), - ), - ttlcache.WithDisableTouchOnHit[string, libregraph.Group](), - ) - go groupsCache.Start() svc := Graph{ config: options.Config, mux: m, logger: &options.Logger, specialDriveItemsCache: spacePropertiesCache, - usersCache: usersCache, - groupsCache: groupsCache, + identityCache: identityCache, eventsPublisher: options.EventsPublisher, gatewaySelector: options.GatewaySelector, searchService: options.SearchService,