package tombstone import ( "context" "strconv" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" lru "github.com/hashicorp/golang-lru/v2" "go.uber.org/zap" ) // Source is a tombstone source interface. type Source interface { // Tombstone must return tombstone from the source it was // configured to fetch from and any error that appeared during // fetching process. // // Tombstone MUST return (nil, nil) if requested tombstone is // missing in the storage for the provided epoch. Tombstone(ctx context.Context, a oid.Address, epoch uint64) (*objectSDK.Object, error) } // ExpirationChecker is a tombstone source wrapper. // It checks tombstones presence via tombstone // source, caches it checks its expiration. // // Must be created via NewChecker function. `var` and // `ExpirationChecker{}` declarations leads to undefined behaviour // and may lead to panics. type ExpirationChecker struct { cache *lru.Cache[string, uint64] log *logger.Logger tsSource Source } // IsTombstoneAvailable checks the tombstone presence in the system in the // following order: // - 1. Local LRU cache; // - 2. Tombstone source. // // If a tombstone was successfully fetched (regardless of its expiration) // it is cached in the LRU cache. func (g *ExpirationChecker) IsTombstoneAvailable(ctx context.Context, a oid.Address, epoch uint64) bool { addrStr := a.EncodeToString() log := g.log.With(zap.String("address", addrStr)) expEpoch, ok := g.cache.Get(addrStr) if ok { return expEpoch > epoch } ts, err := g.tsSource.Tombstone(ctx, a, epoch) if err != nil { log.Warn( logs.TombstoneCouldNotGetTheTombstoneTheSource, zap.Error(err), ) } else { if ts != nil { return g.handleTS(addrStr, ts, epoch) } } // requested tombstone not // found in the FrostFS network return false } func (g *ExpirationChecker) handleTS(addr string, ts *objectSDK.Object, reqEpoch uint64) bool { for _, atr := range ts.Attributes() { if atr.Key() == objectV2.SysAttributeExpEpoch || atr.Key() == objectV2.SysAttributeExpEpochNeoFS { epoch, err := strconv.ParseUint(atr.Value(), 10, 64) if err != nil { g.log.Warn( "tombstone getter: could not parse tombstone expiration epoch", zap.Error(err), ) return false } g.cache.Add(addr, epoch) return epoch >= reqEpoch } } // unexpected tombstone without expiration epoch return false }