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.
This commit is contained in:
Ralf Haferkamp
2023-11-07 16:40:11 +01:00
committed by Ralf Haferkamp
parent f2599dfa76
commit c9df9f5f31
4 changed files with 151 additions and 39 deletions

View File

@@ -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
}

View File

@@ -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()}}
}

View File

@@ -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

View File

@@ -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,