github.com/cs3org/reva/v2@v2.27.7/pkg/share/manager/jsoncs3/providercache/providercache.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 providercache 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "os" 26 "path" 27 "path/filepath" 28 "strings" 29 "sync" 30 "time" 31 32 collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" 33 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 34 "github.com/cs3org/reva/v2/pkg/appctx" 35 "github.com/cs3org/reva/v2/pkg/errtypes" 36 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache" 37 "github.com/cs3org/reva/v2/pkg/storage/utils/metadata" 38 "go.opentelemetry.io/otel" 39 "go.opentelemetry.io/otel/attribute" 40 "go.opentelemetry.io/otel/codes" 41 "go.opentelemetry.io/otel/trace" 42 "golang.org/x/exp/maps" 43 ) 44 45 var tracer trace.Tracer 46 47 func init() { 48 tracer = otel.Tracer("github.com/cs3org/reva/v2/pkg/share/manager/jsoncs3/providercache") 49 } 50 51 // Cache holds share information structured by provider and space 52 type Cache struct { 53 lockMap sync.Map 54 55 Providers mtimesyncedcache.Map[string, *Spaces] 56 57 storage metadata.Storage 58 ttl time.Duration 59 } 60 61 // Spaces holds the share information for provider 62 type Spaces struct { 63 Spaces mtimesyncedcache.Map[string, *Shares] 64 } 65 66 // Shares holds the share information of one space 67 type Shares struct { 68 Shares map[string]*collaboration.Share 69 70 Etag string 71 } 72 73 // UnmarshalJSON overrides the default unmarshaling 74 // Shares are tricky to unmarshal because they contain an interface (Grantee) which makes the json Unmarshal bail out 75 // To work around that problem we unmarshal into json.RawMessage in a first step and then try to manually unmarshal 76 // into the specific types in a second step. 77 func (s *Shares) UnmarshalJSON(data []byte) error { 78 tmp := struct { 79 Shares map[string]json.RawMessage 80 }{} 81 82 err := json.Unmarshal(data, &tmp) 83 if err != nil { 84 return err 85 } 86 87 s.Shares = make(map[string]*collaboration.Share, len(tmp.Shares)) 88 for id, genericShare := range tmp.Shares { 89 userShare := &collaboration.Share{ 90 Grantee: &provider.Grantee{Id: &provider.Grantee_UserId{}}, 91 } 92 err = json.Unmarshal(genericShare, userShare) // is this a user share? 93 if err == nil && userShare.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER { 94 s.Shares[id] = userShare 95 continue 96 } 97 98 groupShare := &collaboration.Share{ 99 Grantee: &provider.Grantee{Id: &provider.Grantee_GroupId{}}, 100 } 101 err = json.Unmarshal(genericShare, groupShare) // is this a group share? 102 if err == nil && groupShare.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { 103 s.Shares[id] = groupShare 104 continue 105 } 106 107 invalidShare := &collaboration.Share{} 108 err = json.Unmarshal(genericShare, invalidShare) // invalid 109 if err == nil { 110 s.Shares[id] = invalidShare 111 continue 112 } 113 114 return err 115 } 116 117 return nil 118 } 119 120 // LockSpace locks the cache for a given space and returns an unlock function 121 func (c *Cache) LockSpace(spaceID string) func() { 122 v, _ := c.lockMap.LoadOrStore(spaceID, &sync.Mutex{}) 123 lock := v.(*sync.Mutex) 124 125 lock.Lock() 126 return func() { lock.Unlock() } 127 } 128 129 // New returns a new Cache instance 130 func New(s metadata.Storage, ttl time.Duration) Cache { 131 return Cache{ 132 Providers: mtimesyncedcache.Map[string, *Spaces]{}, 133 storage: s, 134 ttl: ttl, 135 lockMap: sync.Map{}, 136 } 137 } 138 139 func (c *Cache) isSpaceCached(storageID, spaceID string) bool { 140 spaces, ok := c.Providers.Load(storageID) 141 if !ok { 142 return false 143 } 144 _, ok = spaces.Spaces.Load(spaceID) 145 return ok 146 } 147 148 // Add adds a share to the cache 149 func (c *Cache) Add(ctx context.Context, storageID, spaceID, shareID string, share *collaboration.Share) error { 150 ctx, span := tracer.Start(ctx, "Add") 151 defer span.End() 152 span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID), attribute.String("cs3.shareid", shareID)) 153 154 switch { 155 case storageID == "": 156 return fmt.Errorf("missing storage id") 157 case spaceID == "": 158 return fmt.Errorf("missing space id") 159 case shareID == "": 160 return fmt.Errorf("missing share id") 161 } 162 163 unlock := c.LockSpace(spaceID) 164 defer unlock() 165 span.AddEvent("got lock") 166 167 var err error 168 if !c.isSpaceCached(storageID, spaceID) { 169 err = c.syncWithLock(ctx, storageID, spaceID) 170 if err != nil { 171 return err 172 } 173 } 174 175 log := appctx.GetLogger(ctx).With(). 176 Str("hostname", os.Getenv("HOSTNAME")). 177 Str("storageID", storageID). 178 Str("spaceID", spaceID). 179 Str("shareID", shareID).Logger() 180 181 persistFunc := func() error { 182 183 spaces, _ := c.Providers.Load(storageID) 184 space, _ := spaces.Spaces.Load(spaceID) 185 186 log.Info().Interface("shares", maps.Keys(space.Shares)).Str("New share", shareID).Msg("Adding share to space") 187 space.Shares[shareID] = share 188 189 return c.Persist(ctx, storageID, spaceID) 190 } 191 192 for retries := 100; retries > 0; retries-- { 193 err = persistFunc() 194 switch err.(type) { 195 case nil: 196 span.SetStatus(codes.Ok, "") 197 return nil 198 case errtypes.Aborted: 199 log.Debug().Msg("aborted when persisting added provider share: etag changed. retrying...") 200 // this is the expected status code from the server when the if-match etag check fails 201 // continue with sync below 202 case errtypes.PreconditionFailed: 203 log.Debug().Msg("precondition failed when persisting added provider share: etag changed. retrying...") 204 // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side 205 // continue with sync below 206 case errtypes.AlreadyExists: 207 log.Debug().Msg("already exists when persisting added provider share. retrying...") 208 // CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call. 209 // Thas happens when the cache thinks there is no file. 210 // continue with sync below 211 default: 212 span.SetStatus(codes.Error, fmt.Sprintf("persisting added provider share failed. giving up: %s", err.Error())) 213 log.Error().Err(err).Msg("persisting added provider share failed") 214 return err 215 } 216 if err := c.syncWithLock(ctx, storageID, spaceID); err != nil { 217 span.RecordError(err) 218 span.SetStatus(codes.Error, err.Error()) 219 log.Error().Err(err).Msg("persisting added provider share failed. giving up.") 220 return err 221 } 222 } 223 224 return err 225 } 226 227 // Remove removes a share from the cache 228 func (c *Cache) Remove(ctx context.Context, storageID, spaceID, shareID string) error { 229 ctx, span := tracer.Start(ctx, "Remove") 230 defer span.End() 231 span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID), attribute.String("cs3.shareid", shareID)) 232 233 unlock := c.LockSpace(spaceID) 234 defer unlock() 235 span.AddEvent("got lock") 236 237 if !c.isSpaceCached(storageID, spaceID) { 238 err := c.syncWithLock(ctx, storageID, spaceID) 239 if err != nil { 240 return err 241 } 242 } 243 244 persistFunc := func() error { 245 spaces, ok := c.Providers.Load(storageID) 246 if !ok { 247 return nil 248 } 249 space, _ := spaces.Spaces.Load(spaceID) 250 if !ok { 251 return nil 252 } 253 delete(space.Shares, shareID) 254 255 return c.Persist(ctx, storageID, spaceID) 256 } 257 258 log := appctx.GetLogger(ctx).With(). 259 Str("hostname", os.Getenv("HOSTNAME")). 260 Str("storageID", storageID). 261 Str("spaceID", spaceID). 262 Str("shareID", shareID).Logger() 263 264 var err error 265 for retries := 100; retries > 0; retries-- { 266 err = persistFunc() 267 switch err.(type) { 268 case nil: 269 span.SetStatus(codes.Ok, "") 270 return nil 271 case errtypes.Aborted: 272 log.Debug().Msg("aborted when persisting removed provider share: etag changed. retrying...") 273 // this is the expected status code from the server when the if-match etag check fails 274 // continue with sync below 275 case errtypes.PreconditionFailed: 276 log.Debug().Msg("precondition failed when persisting removed provider share: etag changed. retrying...") 277 // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side 278 // continue with sync below 279 default: 280 span.SetStatus(codes.Error, fmt.Sprintf("persisting removed provider share failed. giving up: %s", err.Error())) 281 log.Error().Err(err).Msg("persisting removed provider share failed") 282 return err 283 } 284 if err := c.syncWithLock(ctx, storageID, spaceID); err != nil { 285 span.RecordError(err) 286 span.SetStatus(codes.Error, err.Error()) 287 log.Error().Err(err).Msg("persisting removed provider share failed. giving up.") 288 return err 289 } 290 } 291 return err 292 } 293 294 // Get returns one entry from the cache 295 func (c *Cache) Get(ctx context.Context, storageID, spaceID, shareID string, skipSync bool) (*collaboration.Share, error) { 296 ctx, span := tracer.Start(ctx, "Get") 297 defer span.End() 298 span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID), attribute.String("cs3.shareid", shareID)) 299 300 unlock := c.LockSpace(spaceID) 301 defer unlock() 302 span.AddEvent("got lock") 303 304 if !skipSync { 305 // sync cache, maybe our data is outdated 306 err := c.syncWithLock(ctx, storageID, spaceID) 307 if err != nil { 308 return nil, err 309 } 310 } 311 312 spaces, ok := c.Providers.Load(storageID) 313 if !ok { 314 return nil, nil 315 } 316 space, ok := spaces.Spaces.Load(spaceID) 317 if !ok { 318 return nil, nil 319 } 320 return space.Shares[shareID], nil 321 } 322 323 // All returns all entries in the storage 324 func (c *Cache) All(ctx context.Context) (*mtimesyncedcache.Map[string, *Spaces], error) { 325 ctx, span := tracer.Start(ctx, "All") 326 defer span.End() 327 328 providers, err := c.storage.ListDir(ctx, "/storages") 329 if err != nil { 330 return nil, err 331 } 332 for _, provider := range providers { 333 storageID := provider.Name 334 spaces, err := c.storage.ListDir(ctx, path.Join("/storages", storageID)) 335 if err != nil { 336 return nil, err 337 } 338 for _, space := range spaces { 339 spaceID := strings.TrimSuffix(space.Name, ".json") 340 341 unlock := c.LockSpace(spaceID) 342 span.AddEvent("got lock for space " + spaceID) 343 if err := c.syncWithLock(ctx, storageID, spaceID); err != nil { 344 return nil, err 345 } 346 unlock() 347 } 348 } 349 350 return &c.Providers, nil 351 } 352 353 // ListSpace returns the list of shares in a given space 354 func (c *Cache) ListSpace(ctx context.Context, storageID, spaceID string) (*Shares, error) { 355 ctx, span := tracer.Start(ctx, "ListSpace") 356 defer span.End() 357 span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID)) 358 359 unlock := c.LockSpace(spaceID) 360 defer unlock() 361 span.AddEvent("got lock") 362 363 // sync cache, maybe our data is outdated 364 err := c.syncWithLock(ctx, storageID, spaceID) 365 if err != nil { 366 return nil, err 367 } 368 369 spaces, ok := c.Providers.Load(storageID) 370 if !ok { 371 return &Shares{}, nil 372 } 373 374 space, ok := spaces.Spaces.Load(spaceID) 375 if !ok { 376 return &Shares{}, nil 377 } 378 379 shares := &Shares{ 380 Shares: maps.Clone(space.Shares), 381 Etag: space.Etag, 382 } 383 return shares, nil 384 } 385 386 // Persist persists the data of one space 387 func (c *Cache) Persist(ctx context.Context, storageID, spaceID string) error { 388 ctx, span := tracer.Start(ctx, "Persist") 389 defer span.End() 390 span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID)) 391 392 spaces, ok := c.Providers.Load(storageID) 393 if !ok { 394 span.AddEvent("nothing to persist") 395 span.SetStatus(codes.Ok, "") 396 return nil 397 } 398 space, ok := spaces.Spaces.Load(spaceID) 399 if !ok { 400 span.AddEvent("nothing to persist") 401 span.SetStatus(codes.Ok, "") 402 return nil 403 } 404 span.SetAttributes(attribute.String("BeforeEtag", space.Etag)) 405 log := appctx.GetLogger(ctx).With().Str("storageID", storageID).Str("spaceID", spaceID).Logger() 406 log = log.With().Str("BeforeEtag", space.Etag).Logger() 407 408 createdBytes, err := json.Marshal(space) 409 if err != nil { 410 span.RecordError(err) 411 span.SetStatus(codes.Error, err.Error()) 412 return err 413 } 414 jsonPath := spaceJSONPath(storageID, spaceID) 415 if err := c.storage.MakeDirIfNotExist(ctx, path.Dir(jsonPath)); err != nil { 416 span.RecordError(err) 417 span.SetStatus(codes.Error, err.Error()) 418 return err 419 } 420 421 span.SetAttributes(attribute.String("etag", space.Etag)) 422 423 ur := metadata.UploadRequest{ 424 Path: jsonPath, 425 Content: createdBytes, 426 IfMatchEtag: space.Etag, 427 } 428 // 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 429 // > If the field value is "*", the condition is false if the origin server has a current representation for the target resource. 430 if space.Etag == "" { 431 ur.IfNoneMatch = []string{"*"} 432 } 433 434 res, err := c.storage.Upload(ctx, ur) 435 if err != nil { 436 span.RecordError(err) 437 span.SetStatus(codes.Error, err.Error()) 438 log.Debug().Err(err).Msg("persisting provider cache failed") 439 return err 440 } 441 space.Etag = res.Etag 442 443 span.SetStatus(codes.Ok, "") 444 shares := []string{} 445 for _, s := range space.Shares { 446 shares = append(shares, s.GetId().GetOpaqueId()) 447 } 448 log.Debug().Str("AfterEtag", space.Etag).Interface("Shares", shares).Msg("persisted provider cache") 449 return nil 450 } 451 452 // PurgeSpace removes a space from the cache 453 func (c *Cache) PurgeSpace(ctx context.Context, storageID, spaceID string) error { 454 ctx, span := tracer.Start(ctx, "PurgeSpace") 455 defer span.End() 456 457 unlock := c.LockSpace(spaceID) 458 defer unlock() 459 span.AddEvent("got lock") 460 461 if !c.isSpaceCached(storageID, spaceID) { 462 err := c.syncWithLock(ctx, storageID, spaceID) 463 if err != nil { 464 return err 465 } 466 } 467 468 spaces, ok := c.Providers.Load(storageID) 469 if !ok { 470 return nil 471 } 472 newShares := &Shares{} 473 if space, ok := spaces.Spaces.Load(spaceID); ok { 474 newShares.Etag = space.Etag // keep the etag to allow overwriting the state on the server 475 } 476 spaces.Spaces.Store(spaceID, newShares) 477 478 return c.Persist(ctx, storageID, spaceID) 479 } 480 481 func (c *Cache) syncWithLock(ctx context.Context, storageID, spaceID string) error { 482 ctx, span := tracer.Start(ctx, "syncWithLock") 483 defer span.End() 484 485 c.initializeIfNeeded(storageID, spaceID) 486 487 spaces, _ := c.Providers.Load(storageID) 488 space, _ := spaces.Spaces.Load(spaceID) 489 span.SetAttributes(attribute.String("cs3.storageid", storageID), attribute.String("cs3.spaceid", spaceID), attribute.String("etag", space.Etag)) 490 log := appctx.GetLogger(ctx).With().Str("storageID", storageID).Str("spaceID", spaceID).Str("etag", space.Etag).Str("hostname", os.Getenv("HOSTNAME")).Logger() 491 492 dlreq := metadata.DownloadRequest{ 493 Path: spaceJSONPath(storageID, spaceID), 494 } 495 // when we know an etag, only download if it changed remotely 496 if space.Etag != "" { 497 dlreq.IfNoneMatch = []string{space.Etag} 498 } 499 500 dlres, err := c.storage.Download(ctx, dlreq) 501 switch err.(type) { 502 case nil: 503 span.AddEvent("updating local cache") 504 case errtypes.NotFound: 505 span.SetStatus(codes.Ok, "") 506 return nil 507 case errtypes.NotModified: 508 span.SetStatus(codes.Ok, "") 509 return nil 510 default: 511 span.RecordError(err) 512 span.SetStatus(codes.Error, "downloading provider cache failed") 513 return err 514 } 515 516 span.AddEvent("updating local cache") 517 newShares := &Shares{} 518 err = json.Unmarshal(dlres.Content, newShares) 519 if err != nil { 520 span.RecordError(err) 521 span.SetStatus(codes.Error, "unmarshaling provider cache failed") 522 log.Error().Err(err).Msg("unmarshaling provider cache failed") 523 return err 524 } 525 newShares.Etag = dlres.Etag 526 527 spaces.Spaces.Store(spaceID, newShares) 528 span.SetStatus(codes.Ok, "") 529 return nil 530 } 531 532 func (c *Cache) initializeIfNeeded(storageID, spaceID string) { 533 spaces, _ := c.Providers.LoadOrStore(storageID, &Spaces{ 534 Spaces: mtimesyncedcache.Map[string, *Shares]{}, 535 }) 536 _, _ = spaces.Spaces.LoadOrStore(spaceID, &Shares{ 537 Shares: map[string]*collaboration.Share{}, 538 }) 539 } 540 541 func spaceJSONPath(storageID, spaceID string) string { 542 return filepath.Join("/storages", storageID, spaceID+".json") 543 }