github.com/cs3org/reva/v2@v2.27.7/pkg/share/manager/jsoncs3/sharecache/sharecache.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package sharecache 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "os" 26 "path" 27 "path/filepath" 28 "sync" 29 "time" 30 31 "go.opentelemetry.io/otel/attribute" 32 "go.opentelemetry.io/otel/codes" 33 "golang.org/x/exp/maps" 34 35 "github.com/cs3org/reva/v2/pkg/appctx" 36 "github.com/cs3org/reva/v2/pkg/errtypes" 37 "github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/shareid" 38 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache" 39 "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" 40 ) 41 42 // name is the Tracer name used to identify this instrumentation library. 43 const tracerName = "sharecache" 44 45 // Cache caches the list of share ids for users/groups 46 // It functions as an in-memory cache with a persistence layer 47 // The storage is sharded by user/group 48 type Cache struct { 49 lockMap sync.Map 50 51 UserShares mtimesyncedcache.Map[string, *UserShareCache] 52 53 storage metadata.Storage 54 namespace string 55 filename string 56 ttl time.Duration 57 } 58 59 // UserShareCache holds the space/share map for one user 60 type UserShareCache struct { 61 UserShares map[string]*SpaceShareIDs 62 63 Etag string 64 } 65 66 // SpaceShareIDs holds the unique list of share ids for a space 67 type SpaceShareIDs struct { 68 IDs map[string]struct{} 69 } 70 71 func (c *Cache) lockUser(userID string) func() { 72 v, _ := c.lockMap.LoadOrStore(userID, &sync.Mutex{}) 73 lock := v.(*sync.Mutex) 74 75 lock.Lock() 76 return func() { lock.Unlock() } 77 } 78 79 // New returns a new Cache instance 80 func New(s metadata.Storage, namespace, filename string, ttl time.Duration) Cache { 81 return Cache{ 82 UserShares: mtimesyncedcache.Map[string, *UserShareCache]{}, 83 storage: s, 84 namespace: namespace, 85 filename: filename, 86 ttl: ttl, 87 lockMap: sync.Map{}, 88 } 89 } 90 91 // Add adds a share to the cache 92 func (c *Cache) Add(ctx context.Context, userid, shareID string) error { 93 ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") 94 unlock := c.lockUser(userid) 95 span.End() 96 span.SetAttributes(attribute.String("cs3.userid", userid)) 97 defer unlock() 98 99 if _, ok := c.UserShares.Load(userid); !ok { 100 err := c.syncWithLock(ctx, userid) 101 if err != nil { 102 return err 103 } 104 } 105 106 ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Add") 107 defer span.End() 108 span.SetAttributes(attribute.String("cs3.userid", userid), attribute.String("cs3.shareid", shareID)) 109 110 storageid, spaceid, _ := shareid.Decode(shareID) 111 ssid := storageid + shareid.IDDelimiter + spaceid 112 113 persistFunc := func() error { 114 c.initializeIfNeeded(userid, ssid) 115 116 // add share id 117 us, _ := c.UserShares.Load(userid) 118 us.UserShares[ssid].IDs[shareID] = struct{}{} 119 return c.Persist(ctx, userid) 120 } 121 122 log := appctx.GetLogger(ctx).With(). 123 Str("hostname", os.Getenv("HOSTNAME")). 124 Str("userID", userid). 125 Str("shareID", shareID).Logger() 126 127 var err error 128 for retries := 100; retries > 0; retries-- { 129 err = persistFunc() 130 switch err.(type) { 131 case nil: 132 span.SetStatus(codes.Ok, "") 133 return nil 134 case errtypes.Aborted: 135 log.Debug().Msg("aborted when persisting added share: etag changed. retrying...") 136 // this is the expected status code from the server when the if-match etag check fails 137 // continue with sync below 138 case errtypes.PreconditionFailed: 139 log.Debug().Msg("precondition failed when persisting added share: etag changed. retrying...") 140 // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side 141 // continue with sync below 142 case errtypes.AlreadyExists: 143 log.Debug().Msg("already exists when persisting added share. retrying...") 144 // CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call. 145 // Thas happens when the cache thinks there is no file. 146 // continue with sync below 147 default: 148 span.SetStatus(codes.Error, fmt.Sprintf("persisting added share failed. giving up: %s", err.Error())) 149 log.Error().Err(err).Msg("persisting added share failed") 150 return err 151 } 152 if err := c.syncWithLock(ctx, userid); err != nil { 153 span.RecordError(err) 154 span.SetStatus(codes.Error, err.Error()) 155 log.Error().Err(err).Msg("persisting added share failed. giving up.") 156 return err 157 } 158 } 159 return err 160 } 161 162 // Remove removes a share for the given user 163 func (c *Cache) Remove(ctx context.Context, userid, shareID string) error { 164 ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") 165 unlock := c.lockUser(userid) 166 span.End() 167 span.SetAttributes(attribute.String("cs3.userid", userid)) 168 defer unlock() 169 170 if _, ok := c.UserShares.Load(userid); ok { 171 err := c.syncWithLock(ctx, userid) 172 if err != nil { 173 return err 174 } 175 } 176 177 ctx, span = appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Remove") 178 defer span.End() 179 span.SetAttributes(attribute.String("cs3.userid", userid), attribute.String("cs3.shareid", shareID)) 180 181 storageid, spaceid, _ := shareid.Decode(shareID) 182 ssid := storageid + shareid.IDDelimiter + spaceid 183 184 persistFunc := func() error { 185 us, loaded := c.UserShares.LoadOrStore(userid, &UserShareCache{ 186 UserShares: map[string]*SpaceShareIDs{}, 187 }) 188 189 if loaded { 190 // remove share id 191 delete(us.UserShares[ssid].IDs, shareID) 192 } 193 194 return c.Persist(ctx, userid) 195 } 196 197 log := appctx.GetLogger(ctx).With(). 198 Str("hostname", os.Getenv("HOSTNAME")). 199 Str("userID", userid). 200 Str("shareID", shareID).Logger() 201 202 var err error 203 for retries := 100; retries > 0; retries-- { 204 err = persistFunc() 205 switch err.(type) { 206 case nil: 207 span.SetStatus(codes.Ok, "") 208 return nil 209 case errtypes.Aborted: 210 log.Debug().Msg("aborted when persisting removed share: etag changed. retrying...") 211 // this is the expected status code from the server when the if-match etag check fails 212 // continue with sync below 213 case errtypes.PreconditionFailed: 214 log.Debug().Msg("precondition failed when persisting removed share: etag changed. retrying...") 215 // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side 216 // continue with sync below 217 case errtypes.AlreadyExists: 218 log.Debug().Msg("file already existed when persisting removed share. retrying...") 219 // CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call. 220 // Thas happens when the cache thinks there is no file. 221 // continue with sync below 222 default: 223 span.SetStatus(codes.Error, fmt.Sprintf("persisting removed share failed. giving up: %s", err.Error())) 224 log.Error().Err(err).Msg("persisting removed share failed") 225 return err 226 } 227 if err := c.syncWithLock(ctx, userid); err != nil { 228 span.RecordError(err) 229 span.SetStatus(codes.Error, err.Error()) 230 return err 231 } 232 } 233 234 return err 235 } 236 237 // List return the list of spaces/shares for the given user/group 238 func (c *Cache) List(ctx context.Context, userid string) (map[string]SpaceShareIDs, error) { 239 ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Grab lock") 240 unlock := c.lockUser(userid) 241 span.End() 242 span.SetAttributes(attribute.String("cs3.userid", userid)) 243 defer unlock() 244 if err := c.syncWithLock(ctx, userid); err != nil { 245 return nil, err 246 } 247 248 r := map[string]SpaceShareIDs{} 249 us, ok := c.UserShares.Load(userid) 250 if !ok { 251 return r, nil 252 } 253 254 for ssid, cached := range us.UserShares { 255 r[ssid] = SpaceShareIDs{ 256 IDs: maps.Clone(cached.IDs), 257 } 258 } 259 return r, nil 260 } 261 262 func (c *Cache) syncWithLock(ctx context.Context, userID string) error { 263 ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Sync") 264 defer span.End() 265 span.SetAttributes(attribute.String("cs3.userid", userID)) 266 267 log := appctx.GetLogger(ctx).With().Str("userID", userID).Logger() 268 269 c.initializeIfNeeded(userID, "") 270 271 userCreatedPath := c.userCreatedPath(userID) 272 span.AddEvent("updating cache") 273 // - update cached list of created shares for the user in memory if changed 274 dlreq := metadata.DownloadRequest{ 275 Path: userCreatedPath, 276 } 277 if us, ok := c.UserShares.Load(userID); ok && us.Etag != "" { 278 dlreq.IfNoneMatch = []string{us.Etag} 279 } 280 281 dlres, err := c.storage.Download(ctx, dlreq) 282 switch err.(type) { 283 case nil: 284 span.AddEvent("updating local cache") 285 case errtypes.NotFound: 286 span.SetStatus(codes.Ok, "") 287 return nil 288 case errtypes.NotModified: 289 span.SetStatus(codes.Ok, "") 290 return nil 291 default: 292 span.SetStatus(codes.Error, fmt.Sprintf("Failed to download the share cache: %s", err.Error())) 293 log.Error().Err(err).Msg("Failed to download the share cache") 294 return err 295 } 296 297 newShareCache := &UserShareCache{} 298 err = json.Unmarshal(dlres.Content, newShareCache) 299 if err != nil { 300 span.SetStatus(codes.Error, fmt.Sprintf("Failed to unmarshal the share cache: %s", err.Error())) 301 log.Error().Err(err).Msg("Failed to unmarshal the share cache") 302 return err 303 } 304 newShareCache.Etag = dlres.Etag 305 306 c.UserShares.Store(userID, newShareCache) 307 span.SetStatus(codes.Ok, "") 308 return nil 309 } 310 311 // Persist persists the data for one user/group to the storage 312 func (c *Cache) Persist(ctx context.Context, userid string) error { 313 ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "Persist") 314 defer span.End() 315 span.SetAttributes(attribute.String("cs3.userid", userid)) 316 317 us, ok := c.UserShares.Load(userid) 318 if !ok { 319 span.SetStatus(codes.Ok, "no user shares") 320 return nil 321 } 322 createdBytes, err := json.Marshal(us) 323 if err != nil { 324 span.RecordError(err) 325 span.SetStatus(codes.Error, err.Error()) 326 return err 327 } 328 jsonPath := c.userCreatedPath(userid) 329 if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { 330 span.RecordError(err) 331 span.SetStatus(codes.Error, err.Error()) 332 return err 333 } 334 335 ur := metadata.UploadRequest{ 336 Path: jsonPath, 337 Content: createdBytes, 338 IfMatchEtag: us.Etag, 339 } 340 // 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 341 // > If the field value is "*", the condition is false if the origin server has a current representation for the target resource. 342 if us.Etag == "" { 343 ur.IfNoneMatch = []string{"*"} 344 } 345 346 res, err := c.storage.Upload(ctx, ur) 347 if err != nil { 348 span.RecordError(err) 349 span.SetStatus(codes.Error, err.Error()) 350 return err 351 } 352 us.Etag = res.Etag 353 354 span.SetStatus(codes.Ok, "") 355 return nil 356 } 357 358 func (c *Cache) userCreatedPath(userid string) string { 359 return filepath.Join("/", c.namespace, userid, c.filename) 360 } 361 362 func (c *Cache) initializeIfNeeded(userid, ssid string) { 363 us, _ := c.UserShares.LoadOrStore(userid, &UserShareCache{ 364 UserShares: map[string]*SpaceShareIDs{}, 365 }) 366 if ssid != "" && us.UserShares[ssid] == nil { 367 us.UserShares[ssid] = &SpaceShareIDs{ 368 IDs: map[string]struct{}{}, 369 } 370 } 371 }