diff --git a/services/settings/pkg/store/metadata/assignments.go b/services/settings/pkg/store/metadata/assignments.go index 61490e8e0fc..19435c372a0 100644 --- a/services/settings/pkg/store/metadata/assignments.go +++ b/services/settings/pkg/store/metadata/assignments.go @@ -66,7 +66,7 @@ func (s *Store) ListRoleAssignments(accountUUID string) ([]*settingsmsg.UserRole func (s *Store) ListRoleAssignmentsByRole(roleID string) ([]*settingsmsg.UserRoleAssignment, error) { s.Init() ctx := context.TODO() - accountIDs, err := s.mdc.ReadDir(ctx, accountsFolderLocation) + cachedAssignments, err := s.assignmentsCache.List(ctx, roleID) switch err.(type) { case nil: // continue @@ -75,43 +75,17 @@ func (s *Store) ListRoleAssignmentsByRole(roleID string) ([]*settingsmsg.UserRol default: return nil, err } - assignments := make([]*settingsmsg.UserRoleAssignment, 0, len(accountIDs)) - - // This is very inefficient, with the current layout we need to iterated through all - // account folders and read each assignment file in there to check if that contains - // the give role ID. - for _, account := range accountIDs { - assignmentIDs, err := s.mdc.ReadDir(ctx, accountPath(account)) - switch err.(type) { - case nil: - // continue - case errtypes.NotFound: - return make([]*settingsmsg.UserRoleAssignment, 0), nil - default: - return nil, err - } - - for _, assignmentID := range assignmentIDs { - b, err := s.mdc.SimpleDownload(ctx, assignmentPath(account, assignmentID)) - switch err.(type) { - case nil: - // continue - case errtypes.NotFound: - continue - default: - return nil, err - } - - a := &settingsmsg.UserRoleAssignment{} - err = json.Unmarshal(b, a) - if err != nil { - return nil, err - } - if a.GetRoleId() == roleID { - assignments = append(assignments, a) - } - } + assignments := make([]*settingsmsg.UserRoleAssignment, 0, len(cachedAssignments)) + for id, v := range cachedAssignments { + assignments = append(assignments, + &settingsmsg.UserRoleAssignment{ + Id: v.AssignmentID, + AccountUuid: id, + RoleId: roleID, + }, + ) } + return assignments, nil } @@ -130,21 +104,42 @@ func (s *Store) WriteRoleAssignment(accountUUID, roleID string) (*settingsmsg.Us return nil, err } + // remove from cache + r, err := s.ListBundles(settingsmsg.Bundle_TYPE_ROLE, []string{}) + if err != nil { + return nil, err + } + for _, role := range r { + err = s.assignmentsCache.Remove(ctx, role.GetId(), accountUUID) + if err != nil { + return nil, err + } + } + err = s.mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID)) if err != nil { return nil, err } - ass := &settingsmsg.UserRoleAssignment{ + assignment := &settingsmsg.UserRoleAssignment{ Id: uuid.Must(uuid.NewV4()).String(), AccountUuid: accountUUID, RoleId: roleID, } - b, err := json.Marshal(ass) + b, err := json.Marshal(assignment) if err != nil { return nil, err } - return ass, s.mdc.SimpleUpload(ctx, assignmentPath(accountUUID, ass.Id), b) + err = s.mdc.SimpleUpload(ctx, assignmentPath(accountUUID, assignment.Id), b) + if err != nil { + return assignment, err + } + + err = s.assignmentsCache.Add(ctx, roleID, assignment) + if err != nil { + return assignment, err + } + return assignment, err } // RemoveRoleAssignment deletes the given role assignment from the existing assignments of the respective account. @@ -163,14 +158,33 @@ func (s *Store) RemoveRoleAssignment(assignmentID string) error { // TODO: use indexer to avoid spamming Metadata service for _, accID := range accounts { - assIDs, err := s.mdc.ReadDir(ctx, accountPath(accID)) + assignmentIDs, err := s.mdc.ReadDir(ctx, accountPath(accID)) if err != nil { // TODO: error? continue } - for _, assID := range assIDs { - if assID == assignmentID { + for _, id := range assignmentIDs { + if id == assignmentID { + b, err := s.mdc.SimpleDownload(ctx, assignmentPath(accID, id)) + switch err.(type) { + case nil: + a := &settingsmsg.UserRoleAssignment{} + if err = json.Unmarshal(b, a); err != nil { + s.Logger.Error().Err(err).Str("assignmentid", id).Msg("failed to unmarshall assignment") + // no return here, as we still want to delete the assignment + } else if err = s.assignmentsCache.Remove(ctx, a.RoleId, accID); err != nil { + s.Logger.Error().Err(err).Str("assignmentid", id).Msg("failed to remove assignment from cache") + // no return here, as we still want to delete the assignment + } + // continue + case errtypes.NotFound: + continue + default: + s.Logger.Error().Err(err).Str("assignmentid", id).Msg("could not download assignment, for cache cleanup") + // We're not returning here, as we still want to delete the assignment + } + // as per https://github.com/owncloud/product/issues/103 "Each user can have exactly one role" // we also have to delete the cached dir listing return s.mdc.Delete(ctx, accountPath(accID)) diff --git a/services/settings/pkg/store/metadata/assignments_test.go b/services/settings/pkg/store/metadata/assignments_test.go index fc430177264..7ea0a7da883 100644 --- a/services/settings/pkg/store/metadata/assignments_test.go +++ b/services/settings/pkg/store/metadata/assignments_test.go @@ -2,14 +2,17 @@ package store import ( "log" + "os" "sync" "testing" + "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" "github.com/gofrs/uuid" olog "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/shared" settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" "github.com/owncloud/ocis/v2/services/settings/pkg/config/defaults" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/metadata/assignmentscache" "github.com/stretchr/testify/require" ) @@ -94,6 +97,10 @@ var ( ) func init() { + tmpdir, _ := os.MkdirTemp("", "assignmentcache-test") + storage, _ := metadata.NewDiskStorage(tmpdir) + + s.assignmentsCache = assignmentscache.New(storage, assignmentCacheFolderLocation, "assignments.json") s.cfg = defaults.DefaultConfig() s.cfg.Commons = &shared.Commons{ AdminUserID: uuid.Must(uuid.NewV4()).String(), @@ -211,10 +218,12 @@ func TestListRoleAssignmentByRole(t *testing.T) { for name, scenario := range scenarios { scenario := scenario t.Run(name, func(t *testing.T) { + assignmentIDs := make([]string, 0, len(scenario.assignments)) for _, a := range scenario.assignments { ass, err := s.WriteRoleAssignment(a.userID, a.roleID) require.NoError(t, err) require.Equal(t, ass.RoleId, a.roleID) + assignmentIDs = append(assignmentIDs, ass.GetId()) } list, err := s.ListRoleAssignmentsByRole(scenario.queryRole) @@ -223,6 +232,15 @@ func TestListRoleAssignmentByRole(t *testing.T) { for _, ass := range list { require.Equal(t, ass.RoleId, scenario.queryRole) } + + for _, a := range assignmentIDs { + err := s.RemoveRoleAssignment(a) + require.NoError(t, err) + } + + list, err = s.ListRoleAssignmentsByRole(scenario.queryRole) + require.NoError(t, err) + require.Equal(t, 0, len(list)) }) } } diff --git a/services/settings/pkg/store/metadata/assignmentscache/assignmentscache.go b/services/settings/pkg/store/metadata/assignmentscache/assignmentscache.go new file mode 100644 index 00000000000..b0230fe6748 --- /dev/null +++ b/services/settings/pkg/store/metadata/assignmentscache/assignmentscache.go @@ -0,0 +1,351 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package assignmentscache + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "sync" + + "github.com/cs3org/reva/v2/pkg/appctx" + "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache" + "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" +) + +// name is the Tracer name used to identify this instrumentation library. +const tracerName = "assignmentscache" + +// Cache caches the list of roleassignments for roles +// It functions as an in-memory cache with a persistence layer +// The storage is sharded by roleid +type Cache struct { + lockMap sync.Map + + RoleAssignments mtimesyncedcache.Map[string, *RoleAssignmentCache] + + storage metadata.Storage + namespace string + filename string +} + +// RoleAssignmentCache holds the assignments for one role +type RoleAssignmentCache struct { + RoleAssignments map[string]*RoleAssignment `json:"roleassignments"` + Etag string `json:"etag"` +} + +// RoleAssignment holds the unique list of assignments ids for a role +type RoleAssignment struct { + AssignmentID string `json:"assignmentid"` +} + +func (c *Cache) lockRole(roleID string) func() { + v, _ := c.lockMap.LoadOrStore(roleID, &sync.Mutex{}) + lock := v.(*sync.Mutex) + + lock.Lock() + return func() { lock.Unlock() } +} + +// New returns a new Cache instance +func New(s metadata.Storage, namespace, filename string) Cache { + return Cache{ + RoleAssignments: mtimesyncedcache.Map[string, *RoleAssignmentCache]{}, + storage: s, + namespace: namespace, + filename: filename, + lockMap: sync.Map{}, + } +} + +// Add adds a role assignment to the cache +func (c *Cache) Add(ctx context.Context, roleID string, assignment *settingsmsg.UserRoleAssignment) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") + unlock := c.lockRole(roleID) + span.End() + span.SetAttributes(attribute.String("roleid", roleID)) + defer unlock() + log := appctx.GetLogger(ctx).With(). + Str("hostname", os.Getenv("HOSTNAME")). + Str("roleID", roleID). + Str("assignmentID", assignment.GetId()).Logger() + + if _, ok := c.RoleAssignments.Load(roleID); !ok { + err := c.syncWithLock(ctx, roleID) + if err != nil { + return err + } + } + + ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") + defer span.End() + span.SetAttributes(attribute.String("roleid", roleID), attribute.String("assignmentid", assignment.GetId())) + + persistFunc := func() error { + c.initializeIfNeeded(roleID, assignment.GetAccountUuid()) + + us, _ := c.RoleAssignments.Load(roleID) + us.RoleAssignments[assignment.GetAccountUuid()] = &RoleAssignment{ + AssignmentID: assignment.GetId(), + } + + return c.Persist(ctx, roleID) + } + + var err error + for retries := 100; retries > 0; retries-- { + err = persistFunc() + switch err.(type) { + case nil: + span.SetStatus(codes.Ok, "") + return nil + case errtypes.Aborted: + log.Debug().Msg("aborted when persisting added assignemt: etag changed. retrying...") + // this is the expected status code from the server when the if-match etag check fails + // continue with sync below + case errtypes.PreconditionFailed: + log.Debug().Msg("precondition failed when persisting added assignemt: etag changed. retrying...") + // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side + // continue with sync below + case errtypes.AlreadyExists: + log.Debug().Msg("already exists when persisting added assignemt. retrying...") + // CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call. + // Thas happens when the cache thinks there is no file. + // continue with sync below + default: + span.SetStatus(codes.Error, "persisting added assignemt failed. giving up: "+err.Error()) + log.Error().Err(err).Msg("persisting added assignemt failed") + return err + } + if err := c.syncWithLock(ctx, roleID); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + log.Error().Err(err).Msg("persisting added assignment failed. giving up.") + return err + } + } + return err +} + +// Remove removes an assignment from the roles cache +func (c *Cache) Remove(ctx context.Context, roleID, accountID string) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") + unlock := c.lockRole(roleID) + span.End() + span.SetAttributes(attribute.String("roleid", roleID)) + defer unlock() + + if _, ok := c.RoleAssignments.Load(roleID); ok { + err := c.syncWithLock(ctx, roleID) + if err != nil { + return err + } + } + + ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Remove") + defer span.End() + span.SetAttributes(attribute.String("roleid", roleID), attribute.String("userid", accountID)) + + persistFunc := func() error { + us, loaded := c.RoleAssignments.LoadOrStore(roleID, &RoleAssignmentCache{ + RoleAssignments: map[string]*RoleAssignment{}, + }) + + if loaded { + // remove user id + delete(us.RoleAssignments, accountID) + } + + return c.Persist(ctx, roleID) + } + + log := appctx.GetLogger(ctx).With(). + Str("hostname", os.Getenv("HOSTNAME")). + Str("roleID", roleID). + Str("accountID", accountID).Logger() + + var err error + for retries := 100; retries > 0; retries-- { + err = persistFunc() + switch err.(type) { + case nil: + span.SetStatus(codes.Ok, "") + return nil + case errtypes.Aborted: + log.Debug().Msg("aborted when persisting removed assignment: etag changed. retrying...") + // this is the expected status code from the server when the if-match etag check fails + // continue with sync below + case errtypes.PreconditionFailed: + log.Debug().Msg("precondition failed when persisting removed assignment: etag changed. retrying...") + // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side + // continue with sync below + default: + span.SetStatus(codes.Error, fmt.Sprintf("persisting removed assignment failed. giving up: %s", err.Error())) + log.Error().Err(err).Msg("persisting removed assignment failed") + return err + } + if err := c.syncWithLock(ctx, roleID); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + } + + return err +} + +// List return the list of assignments for the given role +func (c *Cache) List(ctx context.Context, roleID string) (map[string]RoleAssignment, error) { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") + unlock := c.lockRole(roleID) + span.End() + span.SetAttributes(attribute.String("roleid", roleID)) + defer unlock() + if err := c.syncWithLock(ctx, roleID); err != nil { + return nil, err + } + + r := map[string]RoleAssignment{} + us, ok := c.RoleAssignments.Load(roleID) + if !ok { + return r, nil + } + + for roleid, cached := range us.RoleAssignments { + r[roleid] = *cached + } + return r, nil +} + +func (c *Cache) syncWithLock(ctx context.Context, roleID string) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Sync") + defer span.End() + span.SetAttributes(attribute.String("roleid", roleID)) + + log := appctx.GetLogger(ctx).With().Str("roleID", roleID).Logger() + + c.initializeIfNeeded(roleID, "") + + assignmentsCachePath := c.assignmentsForRolePath(roleID) + span.AddEvent("updating cache") + // - update cached list of assignments for the role in memory if changed + dlreq := metadata.DownloadRequest{ + Path: assignmentsCachePath, + } + if us, ok := c.RoleAssignments.Load(roleID); ok && us.Etag != "" { + dlreq.IfNoneMatch = []string{us.Etag} + } + + dlres, err := c.storage.Download(ctx, dlreq) + switch err.(type) { + case nil: + span.AddEvent("updating local cache") + case errtypes.NotFound: + span.SetStatus(codes.Ok, "") + return nil + case errtypes.NotModified: + span.SetStatus(codes.Ok, "") + return nil + default: + span.SetStatus(codes.Error, fmt.Sprintf("Failed to download the assignment cache: %s", err.Error())) + log.Error().Err(err).Msg("Failed to download the assignment cache") + return err + } + + assignmentCache := &RoleAssignmentCache{} + err = json.Unmarshal(dlres.Content, assignmentCache) + if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf("Failed to unmarshal the assignment cache: %s", err.Error())) + log.Error().Err(err).Msg("Failed to unmarshal the assignment cache") + return err + } + assignmentCache.Etag = dlres.Etag + + c.RoleAssignments.Store(roleID, assignmentCache) + span.SetStatus(codes.Ok, "") + return nil +} + +// Persist persists the data for one role to the storage +func (c *Cache) Persist(ctx context.Context, roleID string) error { + ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Persist") + defer span.End() + span.SetAttributes(attribute.String("roleid", roleID)) + + ra, ok := c.RoleAssignments.Load(roleID) + if !ok { + span.SetStatus(codes.Ok, "no role assignments") + return nil + } + createdBytes, err := json.Marshal(ra) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + jsonPath := c.assignmentsForRolePath(roleID) + if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + ur := metadata.UploadRequest{ + Path: jsonPath, + Content: createdBytes, + IfMatchEtag: ra.Etag, + } + // when there is no etag in memory make sure the file has not been created on the server, see https://www.rfc-editor.org/rfc/rfc9110#field.if-match + // > If the field value is "*", the condition is false if the origin server has a current representation for the target resource. + if ra.Etag == "" { + ur.IfNoneMatch = []string{"*"} + } + + res, err := c.storage.Upload(ctx, ur) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + ra.Etag = res.Etag + + span.SetStatus(codes.Ok, "") + return nil +} + +func (c *Cache) assignmentsForRolePath(roleid string) string { + return filepath.Join("/", c.namespace, roleid, c.filename) +} + +func (c *Cache) initializeIfNeeded(roleID, accountID string) { + us, _ := c.RoleAssignments.LoadOrStore(roleID, &RoleAssignmentCache{ + RoleAssignments: map[string]*RoleAssignment{}, + }) + if accountID != "" && us.RoleAssignments[accountID] == nil { + us.RoleAssignments[accountID] = &RoleAssignment{} + } +} diff --git a/services/settings/pkg/store/metadata/assignmentscache/assignmentscache_suite_test.go b/services/settings/pkg/store/metadata/assignmentscache/assignmentscache_suite_test.go new file mode 100644 index 00000000000..b9cbcd41493 --- /dev/null +++ b/services/settings/pkg/store/metadata/assignmentscache/assignmentscache_suite_test.go @@ -0,0 +1,13 @@ +package assignmentscache_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestAssignmentscache(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Assignmentscache Suite") +} diff --git a/services/settings/pkg/store/metadata/assignmentscache/assignmentscache_test.go b/services/settings/pkg/store/metadata/assignmentscache/assignmentscache_test.go new file mode 100644 index 00000000000..7762762a11a --- /dev/null +++ b/services/settings/pkg/store/metadata/assignmentscache/assignmentscache_test.go @@ -0,0 +1,72 @@ +package assignmentscache_test + +import ( + "context" + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" + settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/metadata/assignmentscache" +) + +var _ = Describe("Assignmentscache", func() { + var ( + c assignmentscache.Cache + storage metadata.Storage + + roleid = "11111111-1111-1111-1111-111111111111" + assignment = &settingsmsg.UserRoleAssignment{ + AccountUuid: "00000000-0000-0000-0000-000000000001", + RoleId: roleid, + Id: "00000001-0000-0000-0000-000000000000", + } + ctx context.Context + tmpdir string + ) + + BeforeEach(func() { + ctx = context.Background() + + var err error + tmpdir, err = os.MkdirTemp("", "assignmentscache-test") + Expect(err).ToNot(HaveOccurred()) + + err = os.MkdirAll(tmpdir, 0755) + Expect(err).ToNot(HaveOccurred()) + + storage, err = metadata.NewDiskStorage(tmpdir) + Expect(err).ToNot(HaveOccurred()) + + c = assignmentscache.New(storage, "basename", "assignments.json") + Expect(c).ToNot(BeNil()) //nolint:all + }) + + AfterEach(func() { + if tmpdir != "" { + os.RemoveAll(tmpdir) + } + }) + + Describe("Persist", func() { + Context("with an existing entry", func() { + BeforeEach(func() { + Expect(c.Add(ctx, roleid, assignment)).To(Succeed()) + }) + + It("updates the etag", func() { + ra, _ := c.RoleAssignments.Load(roleid) + oldEtag := ra.Etag + Expect(oldEtag).ToNot(BeEmpty()) + + Expect(c.Persist(ctx, roleid)).To(Succeed()) + + ra, _ = c.RoleAssignments.Load(roleid) + Expect(ra.Etag).ToNot(Equal(oldEtag)) + }) + }) + }) + +}) diff --git a/services/settings/pkg/store/metadata/store.go b/services/settings/pkg/store/metadata/store.go index 3985e84a1d4..a98d2f1ccc4 100644 --- a/services/settings/pkg/store/metadata/store.go +++ b/services/settings/pkg/store/metadata/store.go @@ -15,17 +15,19 @@ import ( "github.com/owncloud/ocis/v2/services/settings/pkg/config" "github.com/owncloud/ocis/v2/services/settings/pkg/settings" "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" + "github.com/owncloud/ocis/v2/services/settings/pkg/store/metadata/assignmentscache" ) var ( // Name is the default name for the settings store - Name = "ocis-settings" - managerName = "metadata" - settingsSpaceID = "f1bdd61a-da7c-49fc-8203-0558109d1b4f" // uuid.Must(uuid.NewV4()).String() - rootFolderLocation = "settings" - bundleFolderLocation = "settings/bundles" - accountsFolderLocation = "settings/accounts" - valuesFolderLocation = "settings/values" + Name = "ocis-settings" + managerName = "metadata" + settingsSpaceID = "f1bdd61a-da7c-49fc-8203-0558109d1b4f" // uuid.Must(uuid.NewV4()).String() + rootFolderLocation = "settings" + assignmentCacheFolderLocation = "settings/assignments-cache" + bundleFolderLocation = "settings/bundles" + accountsFolderLocation = "settings/accounts" + valuesFolderLocation = "settings/values" ) // MetadataClient is the interface to talk to metadata service @@ -40,10 +42,10 @@ type MetadataClient interface { // Store interacts with the filesystem to manage settings information type Store struct { - Logger olog.Logger - - mdc MetadataClient - cfg *config.Config + Logger olog.Logger + assignmentsCache assignmentscache.Cache + mdc MetadataClient + cfg *config.Config l *sync.Mutex } @@ -69,6 +71,21 @@ func (s *Store) Init() { if err := s.initMetadataClient(mdc); err != nil { s.Logger.Error().Err(err).Msg("error initializing metadata client") } + client, err := metadata.NewCS3Storage( + s.cfg.Metadata.GatewayAddress, + s.cfg.Metadata.StorageAddress, + s.cfg.Metadata.SystemUserID, + s.cfg.Metadata.SystemUserIDP, + s.cfg.Metadata.SystemUserAPIKey, + ) + if err != nil { + panic(err) + } + if err = client.Init(context.Background(), settingsSpaceID); err != nil { + panic(err) + } + s.assignmentsCache = assignmentscache.New(client, assignmentCacheFolderLocation, "assignments.json") + } // New creates a new store @@ -107,6 +124,7 @@ func (s *Store) initMetadataClient(mdc MetadataClient) error { for _, p := range []string{ rootFolderLocation, + assignmentCacheFolderLocation, accountsFolderLocation, bundleFolderLocation, valuesFolderLocation,