diff --git a/api/apc/lsmsg.go b/api/apc/lsmsg.go index c1eb566595c..ddad3bc5ff3 100644 --- a/api/apc/lsmsg.go +++ b/api/apc/lsmsg.go @@ -1,6 +1,6 @@ // Package apc: API control messages and constants /* - * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2018-2025, NVIDIA CORPORATION. All rights reserved. */ package apc @@ -91,7 +91,7 @@ const ( ) const ( - // Status + // location _status_ LocOK = iota LocMisplacedNode LocMisplacedMountpath @@ -105,6 +105,8 @@ const ( EntryIsArchive = 1 << (EntryStatusBits + 4) EntryVerChanged = 1 << (EntryStatusBits + 5) // see also: QparamLatestVer, et al. EntryVerRemoved = 1 << (EntryStatusBits + 6) // ditto + // added v3.26 + EntryHeadFail = 1 << (EntryStatusBits + 7) ) // ObjEntry.Flags field diff --git a/cmd/cli/cli/scrub.go b/cmd/cli/cli/scrub.go index c600643d552..b9c58996f33 100644 --- a/cmd/cli/cli/scrub.go +++ b/cmd/cli/cli/scrub.go @@ -254,7 +254,7 @@ func (ctx *scrCtx) ls(bck cmn.Bck) (*scrBp, error) { } ) scr.Cname = bck.Cname("") - propNames := []string{apc.GetPropsName, apc.GetPropsSize, apc.GetPropsAtime, apc.GetPropsCopies, apc.GetPropsLocation, apc.GetPropsCustom} + propNames := []string{apc.GetPropsName, apc.GetPropsSize, apc.GetPropsVersion, apc.GetPropsCopies, apc.GetPropsLocation, apc.GetPropsCustom} if bck.IsRemote() { lsmsg.Flags |= apc.LsVerChanged lsmsg.AddProps(propNames...) diff --git a/cmd/cli/teb/scrub.go b/cmd/cli/teb/scrub.go index b0e086ab07f..bf16e28a0a7 100644 --- a/cmd/cli/teb/scrub.go +++ b/cmd/cli/teb/scrub.go @@ -23,8 +23,8 @@ const ( colMissingCp = "MISSING-CP" colSmallSz = "SMALL" colLargeSz = "LARGE" - colVchanged = "VERSION-CHANGED" - colVremoved = "VERSION-REMOVED" + colVchanged = "VER-CHANGED" + colVremoved = "VER-REMOVED" ) const ( diff --git a/cmn/objattrs.go b/cmn/objattrs.go index aa9586e15be..aadc988148d 100644 --- a/cmn/objattrs.go +++ b/cmn/objattrs.go @@ -253,8 +253,11 @@ func (oa *ObjAttrs) FromHeader(hdr http.Header) (cksum *cos.Cksum) { // b) the same remote "source" and at least one matching checksum, or c) two matching checksums. // (See also note below.) // -// Note that mismatch in any given checksum type immediately renders inequality and return -// from the function. +// Note that ETag, checksum, or version mismatch leads to immediate return with error +// specifying the exact cause. +// +// Note version comparison may fail even when the objects are identical, content-wise: +// same size, ETag, and checksums may still "co-exist" with different versions. // // TODO: count == 1 with matching checksum being xxhash - must be configurable :NOTE func (oa *ObjAttrs) CheckEq(rem cos.OAH) error { diff --git a/cmn/objlist_utils.go b/cmn/objlist_utils.go index 4f0029e6c2e..8fd4bad6e41 100644 --- a/cmn/objlist_utils.go +++ b/cmn/objlist_utils.go @@ -1,7 +1,7 @@ // Package cmn provides common constants, types, and utilities for AIS clients // and AIStore. /* - * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2018-2025, NVIDIA CORPORATION. All rights reserved. */ package cmn @@ -67,11 +67,8 @@ func appSorted(entries LsoEntries, ne *LsoEnt) LsoEntries { func (be *LsoEnt) IsPresent() bool { return be.Flags&apc.EntryIsCached != 0 } func (be *LsoEnt) SetPresent() { be.Flags |= apc.EntryIsCached } -// see also: "latest-ver", QparamLatestVer, et al. -func (be *LsoEnt) SetVerChanged() { be.Flags |= apc.EntryVerChanged } -func (be *LsoEnt) IsVerChanged() bool { return be.Flags&apc.EntryVerChanged != 0 } -func (be *LsoEnt) SetVerRemoved() { be.Flags |= apc.EntryVerRemoved } -func (be *LsoEnt) IsVerRemoved() bool { return be.Flags&apc.EntryVerRemoved != 0 } +func (be *LsoEnt) SetFlag(fl uint16) { be.Flags |= fl } +func (be *LsoEnt) IsAnyFlagSet(fl uint16) bool { return be.Flags&fl != 0 } func (be *LsoEnt) IsStatusOK() bool { return be.Status() == 0 } func (be *LsoEnt) Status() uint16 { return be.Flags & apc.EntryStatusMask } diff --git a/core/ldp.go b/core/ldp.go index a5f115dd3e1..1641cf10d6a 100644 --- a/core/ldp.go +++ b/core/ldp.go @@ -1,6 +1,6 @@ // Package core provides core metadata and in-cluster API /* - * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2018-2025, NVIDIA CORPORATION. All rights reserved. */ package core @@ -143,7 +143,7 @@ func (lom *LOM) CheckRemoteMD(locked, sync bool, origReq *http.Request) (res CRM if !locked { // return info (neq and, possibly, not-found), and be done - return CRMD{ErrCode: ecode, Err: err} + return CRMD{ObjAttrs: oa, ErrCode: ecode, Err: err} } // rm remotely-deleted @@ -160,7 +160,7 @@ func (lom *LOM) CheckRemoteMD(locked, sync bool, origReq *http.Request) (res CRM } lom.Uncache() - return CRMD{ErrCode: ecode, Err: err} + return CRMD{ObjAttrs: oa, ErrCode: ecode, Err: err} } // NOTE: Sync is false (ie., not deleting) diff --git a/xact/xs/nextpage.go b/xact/xs/nextpage.go index fb243b10704..214c1b6b81d 100644 --- a/xact/xs/nextpage.go +++ b/xact/xs/nextpage.go @@ -1,7 +1,7 @@ // Package xs is a collection of eXtended actions (xactions), including multi-object // operations, list-objects, (cluster) rebalance and (target) resilver, ETL, and more. /* - * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2018-2025, NVIDIA CORPORATION. All rights reserved. */ package xs @@ -112,19 +112,19 @@ func (npg *npgCtx) nextPageR(nentries cmn.LsoEntries, inclStatusLocalMD bool) (l func (npg *npgCtx) populate(lst *cmn.LsoRes) error { post := npg.wi.lomVisitedCb - for _, obj := range lst.Entries { - if obj.IsDir() { + for _, en := range lst.Entries { + if en.IsDir() { // collecting virtual dir-s when apc.LsNoRecursion is on - skipping here continue } - si, err := npg.wi.smap.HrwName2T(npg.bck.MakeUname(obj.Name)) + si, err := npg.wi.smap.HrwName2T(npg.bck.MakeUname(en.Name)) if err != nil { return err } if si.ID() != core.T.SID() { continue } - lom := core.AllocLOM(obj.Name) + lom := core.AllocLOM(en.Name) if err := lom.InitBck(npg.bck.Bucket()); err != nil { core.FreeLOM(lom) if cmn.IsErrBucketNought(err) { @@ -137,8 +137,8 @@ func (npg *npgCtx) populate(lst *cmn.LsoRes) error { continue } - npg.wi.setWanted(obj, lom) - obj.SetPresent() + npg.wi.setWanted(en, lom) + en.SetPresent() if post != nil { post(lom) diff --git a/xact/xs/wanted_lso.go b/xact/xs/wanted_lso.go index 7a931e82bff..8b7fa4e4be8 100644 --- a/xact/xs/wanted_lso.go +++ b/xact/xs/wanted_lso.go @@ -1,7 +1,7 @@ // Package xs contains most of the supported eXtended actions (xactions) with some // exceptions that include certain storage services (mirror, EC) and extensions (downloader, lru). /* - * Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved. */ package xs @@ -35,7 +35,10 @@ func wanted(msg *apc.LsoMsg) (flags cos.BitFlags) { return } -func (wi *walkInfo) setWanted(e *cmn.LsoEnt, lom *core.LOM) { +func (wi *walkInfo) setWanted(en *cmn.LsoEnt, lom *core.LOM) { + var ( + checkVchanged = wi.msg.IsFlagSet(apc.LsVerChanged) + ) for name, fl := range allmap { if !wi.wanted.IsSet(fl) { continue @@ -46,41 +49,51 @@ func (wi *walkInfo) setWanted(e *cmn.LsoEnt, lom *core.LOM) { case apc.GetPropsCached: // via obj.SetPresent() case apc.GetPropsSize: - if e.Size > 0 && lom.Lsize() != e.Size { - e.SetVerChanged() + if en.Size > 0 && lom.Lsize() != en.Size { + en.SetFlag(apc.EntryVerChanged) } - e.Size = lom.Lsize() + en.Size = lom.Lsize() case apc.GetPropsVersion: - e.Version = lom.Version() + // remote VersionObjMD takes precedence over ais incremental numbering + if en.Version == "" { + en.Version = lom.Version() + } case apc.GetPropsChecksum: - e.Checksum = lom.Checksum().Value() + en.Checksum = lom.Checksum().Value() case apc.GetPropsAtime: - e.Atime = cos.FormatNanoTime(lom.AtimeUnix(), wi.msg.TimeFormat) + // atime vs remote LastModified + en.Atime = cos.FormatNanoTime(lom.AtimeUnix(), wi.msg.TimeFormat) case apc.GetPropsLocation: - e.Location = lom.Location() + en.Location = lom.Location() case apc.GetPropsCopies: - e.Copies = int16(lom.NumCopies()) + en.Copies = int16(lom.NumCopies()) case apc.GetPropsEC: - // TODO?: risk of significant slow-down loading EC metafiles + // TODO at the risk of significant slow-down case apc.GetPropsCustom: - if md := lom.GetCustomMD(); len(md) > 0 { - e.Custom = cmn.CustomMD2S(md) + // en.Custom is set via one of the two alternative flows: + // - checkRemoteMD => HEAD(obj) + // - backend.List* api call + if en.Custom == "" { + if md := lom.GetCustomMD(); len(md) > 0 { + en.Custom = cmn.CustomMD2S(md) + checkVchanged = false + } } default: debug.Assert(false, name) } } - if wi.msg.IsFlagSet(apc.LsVerChanged) && !e.IsVerChanged() { - // - // slow path: extensive 'version-changed' check - // - cmn.S2CustomMD(wi.custom, e.Custom, e.Version) + + // slow path: extensive 'version-changed' check + if checkVchanged && !en.IsAnyFlagSet(apc.EntryVerChanged|apc.EntryVerRemoved) { + cmn.S2CustomMD(wi.custom, en.Custom, en.Version) if len(wi.custom) > 0 { - oa := cmn.ObjAttrs{Size: e.Size, CustomMD: wi.custom} + oa := cmn.ObjAttrs{Size: en.Size, CustomMD: wi.custom} if lom.CheckEq(&oa) != nil { - e.SetVerChanged() + // lom.CheckEq returned err contains the cause + en.SetFlag(apc.EntryVerChanged) } clear(wi.custom) } diff --git a/xact/xs/wi_lso.go b/xact/xs/wi_lso.go index 5499abc1eaf..432439d8e60 100644 --- a/xact/xs/wi_lso.go +++ b/xact/xs/wi_lso.go @@ -88,53 +88,66 @@ func (wi *walkInfo) match(objName string) bool { } // new entry to be added to the listed page (note: slow path) -func (wi *walkInfo) ls(lom *core.LOM, status uint16) (e *cmn.LsoEnt) { - e = &cmn.LsoEnt{Name: lom.ObjName, Flags: status | apc.EntryIsCached} +func (wi *walkInfo) ls(lom *core.LOM, status uint16) (en *cmn.LsoEnt) { + en = &cmn.LsoEnt{Name: lom.ObjName, Flags: status | apc.EntryIsCached} if lom.IsFntl() { orig := lom.OrigFntl() if orig != nil { saved := lom.PushFntl(orig) if wi.msg.IsFlagSet(apc.LsVerChanged) { - checkRemoteMD(lom, e) + checkRemoteMD(lom, en) } lom.PopFntl(saved) - e.Name = orig[1] + en.Name = orig[1] } } else if wi.msg.IsFlagSet(apc.LsVerChanged) { - checkRemoteMD(lom, e) + // may set en.custom and en.version + checkRemoteMD(lom, en) } if wi.msg.IsFlagSet(apc.LsNameOnly) { return } - wi.setWanted(e, lom) + + // fill out even more of `en` + wi.setWanted(en, lom) + wi.lomVisitedCb(lom) return } // NOTE: slow path if lom.Bck is remote -func checkRemoteMD(lom *core.LOM, e *cmn.LsoEnt) { +func checkRemoteMD(lom *core.LOM, en *cmn.LsoEnt) { res := lom.CheckRemoteMD(false /*locked*/, false /*sync*/, nil /*origReq*/) + switch { case res.Eq: debug.AssertNoErr(res.Err) case cos.IsNotExist(res.Err, res.ErrCode): - e.SetVerRemoved() + en.SetFlag(apc.EntryVerRemoved) + case res.Err == nil: + en.SetFlag(apc.EntryVerChanged) + + // expecting custom and version set + debug.Assert(len(res.ObjAttrs.CustomMD) > 0) + en.Custom = cmn.CustomMD2S(res.ObjAttrs.CustomMD) + debug.Assert(res.ObjAttrs.Ver != nil) + en.Version = *res.ObjAttrs.Ver default: - e.SetVerChanged() + en.SetFlag(apc.EntryHeadFail) } } // Performs a number of syscalls to load object metadata. -func (wi *walkInfo) callback(fqn string, de fs.DirEntry) (entry *cmn.LsoEnt, err error) { +func (wi *walkInfo) callback(fqn string, de fs.DirEntry) (en *cmn.LsoEnt, err error) { if de.IsDir() { return } lom := core.AllocLOM("") - entry, err = wi._cb(lom, fqn) + en, err = wi._cb(lom, fqn) core.FreeLOM(lom) - return entry, err + return en, err } func (wi *walkInfo) _cb(lom *core.LOM, fqn string) (*cmn.LsoEnt, error) {