forked from TrueCloudLab/frostfs-node
If context has already been canceled, then there is no need to check other shards. At the same time, it is necessary to avoid handling context cancellation in each handler. Therefore, the context check has been moved to the shard iteration method, which now returns an error. Change-Id: I70030ace36593ce7d2b8376bee39fe82e9dbf88f Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
153 lines
4.4 KiB
Go
153 lines
4.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// HeadPrm groups the parameters of Head operation.
|
|
type HeadPrm struct {
|
|
addr oid.Address
|
|
raw bool
|
|
}
|
|
|
|
// HeadRes groups the resulting values of Head operation.
|
|
type HeadRes struct {
|
|
head *objectSDK.Object
|
|
}
|
|
|
|
// WithAddress is a Head option to set the address of the requested object.
|
|
//
|
|
// Option is required.
|
|
func (p *HeadPrm) WithAddress(addr oid.Address) {
|
|
p.addr = addr
|
|
}
|
|
|
|
// WithRaw is a Head option to set raw flag value. If flag is unset, then Head
|
|
// returns the header of the virtual object, otherwise it returns SplitInfo of the virtual
|
|
// object.
|
|
func (p *HeadPrm) WithRaw(raw bool) {
|
|
p.raw = raw
|
|
}
|
|
|
|
// Header returns the requested object header.
|
|
//
|
|
// Instance has empty payload.
|
|
func (r HeadRes) Header() *objectSDK.Object {
|
|
return r.head
|
|
}
|
|
|
|
// Head reads object header from local storage.
|
|
//
|
|
// Returns any error encountered that
|
|
// did not allow to completely read the object header.
|
|
//
|
|
// Returns an error of type apistatus.ObjectNotFound if the requested object is missing in local storage.
|
|
// Returns an error of type apistatus.ObjectAlreadyRemoved if the requested object was inhumed.
|
|
//
|
|
// Returns an error if executions are blocked (see BlockExecution).
|
|
func (e *StorageEngine) Head(ctx context.Context, prm HeadPrm) (res HeadRes, err error) {
|
|
err = e.execIfNotBlocked(func() error {
|
|
res, err = e.head(ctx, prm)
|
|
return err
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
func (e *StorageEngine) head(ctx context.Context, prm HeadPrm) (HeadRes, error) {
|
|
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.head")
|
|
defer span.End()
|
|
defer elapsed("Head", e.metrics.AddMethodDuration)()
|
|
|
|
var (
|
|
head *objectSDK.Object
|
|
siErr *objectSDK.SplitInfoError
|
|
outSI *objectSDK.SplitInfo
|
|
eiErr *objectSDK.ECInfoError
|
|
outEI *objectSDK.ECInfo
|
|
outError error = new(apistatus.ObjectNotFound)
|
|
shPrm shard.HeadPrm
|
|
)
|
|
shPrm.SetAddress(prm.addr)
|
|
shPrm.SetRaw(prm.raw)
|
|
|
|
if err := e.iterateOverSortedShards(ctx, prm.addr, func(_ int, sh hashedShard) (stop bool) {
|
|
shPrm.ShardLooksBad = sh.errorCount.Load() >= e.errorsThreshold
|
|
res, err := sh.Head(ctx, shPrm)
|
|
if err != nil {
|
|
switch {
|
|
case client.IsErrObjectNotFound(err):
|
|
return false // ignore, go to next shard
|
|
case errors.As(err, &siErr):
|
|
if outSI == nil {
|
|
outSI = objectSDK.NewSplitInfo()
|
|
}
|
|
util.MergeSplitInfo(siErr.SplitInfo(), outSI)
|
|
_, withLink := outSI.Link()
|
|
_, withLast := outSI.LastPart()
|
|
// stop iterating over shards if SplitInfo structure is complete
|
|
if withLink && withLast {
|
|
return true
|
|
}
|
|
return false
|
|
case errors.As(err, &eiErr):
|
|
if outEI == nil {
|
|
outEI = objectSDK.NewECInfo()
|
|
}
|
|
util.MergeECInfo(eiErr.ECInfo(), outEI)
|
|
// stop iterating over shards if ECInfo structure is complete
|
|
return len(outEI.Chunks) == int(outEI.Chunks[0].Total)
|
|
case client.IsErrObjectAlreadyRemoved(err):
|
|
outError = err
|
|
return true // stop, return it back
|
|
case shard.IsErrObjectExpired(err):
|
|
// object is found but should not
|
|
// be returned
|
|
outError = new(apistatus.ObjectNotFound)
|
|
return true
|
|
default:
|
|
e.reportShardError(ctx, sh, "could not head object from shard", err, zap.Stringer("address", prm.addr))
|
|
return false
|
|
}
|
|
}
|
|
head = res.Object()
|
|
return true
|
|
}); err != nil {
|
|
return HeadRes{}, err
|
|
}
|
|
|
|
if head != nil {
|
|
return HeadRes{head: head}, nil
|
|
}
|
|
if outSI != nil {
|
|
return HeadRes{}, logicerr.Wrap(objectSDK.NewSplitInfoError(outSI))
|
|
}
|
|
if outEI != nil {
|
|
return HeadRes{}, logicerr.Wrap(objectSDK.NewECInfoError(outEI))
|
|
}
|
|
return HeadRes{}, outError
|
|
}
|
|
|
|
// Head reads object header from local storage by provided address.
|
|
func Head(ctx context.Context, storage *StorageEngine, addr oid.Address) (*objectSDK.Object, error) {
|
|
var headPrm HeadPrm
|
|
headPrm.WithAddress(addr)
|
|
|
|
res, err := storage.Head(ctx, headPrm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res.Header(), nil
|
|
}
|