package blobtree import ( "context" "os" "path/filepath" "time" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) func (b *BlobTree) Delete(ctx context.Context, prm common.DeletePrm) (common.DeleteRes, error) { var ( success = false startedAt = time.Now() ) defer func() { b.cfg.metrics.Delete(time.Since(startedAt), success, prm.StorageID != nil) }() _, span := tracing.StartSpanFromContext(ctx, "BlobTree.Delete", trace.WithAttributes( attribute.String("path", b.cfg.rootPath), attribute.String("address", prm.Address.EncodeToString()), attribute.String("storage_id", string(prm.StorageID)), )) defer span.End() if b.cfg.readOnly { return common.DeleteRes{}, common.ErrReadOnly } var res common.DeleteRes var err error if path, ok := getPathFromStorageID(prm.StorageID); ok { res, err = b.deleteFromPath(prm.Address, path) } else { res, err = b.findAndDelete(prm.Address) } success = err == nil return res, err } func (b *BlobTree) deleteFromPath(addr oid.Address, path string) (common.DeleteRes, error) { b.fileLock.Lock(path) defer b.fileLock.Unlock(path) dir, idx, err := b.parsePath(path) if err != nil { return common.DeleteRes{}, err } records, err := b.readFileContent(path) if err != nil { return common.DeleteRes{}, err } deleteIdx := -1 for i := range records { if records[i].Address.Equals(addr) { deleteIdx = i break } } if deleteIdx == -1 { return common.DeleteRes{}, logicerr.Wrap(new(apistatus.ObjectNotFound)) } if len(records) == 1 { err = os.Remove(b.getSystemPath(path)) if err == nil { b.dispatcher.ReturnIdx(dir, idx) b.cfg.metrics.DecFilesCount() } return common.DeleteRes{}, err } records = append(records[:deleteIdx], records[deleteIdx+1:]...) size, err := b.writeToTmpAndRename(records, path) if err != nil { return common.DeleteRes{}, err } if size < b.cfg.targetFileSizeBytes { b.dispatcher.ReturnIdx(dir, idx) } return common.DeleteRes{}, nil } func (b *BlobTree) findAndDelete(addr oid.Address) (common.DeleteRes, error) { dir := b.getDir(addr) idx, err := b.findFileIdx(dir, addr) if err != nil { return common.DeleteRes{}, err } return b.deleteFromPath(addr, b.getFilePath(dir, idx)) } func (b *BlobTree) findFileIdx(dir string, addr oid.Address) (uint64, error) { entities, err := os.ReadDir(filepath.Join(b.cfg.rootPath, dir)) if err != nil { if os.IsNotExist(err) { return 0, logicerr.Wrap(new(apistatus.ObjectNotFound)) } return 0, err } for _, entity := range entities { if entity.IsDir() { continue } if b.isTempFile(entity.Name()) { continue } idx, err := b.parseIdx(entity.Name()) if err != nil { continue } path := b.getFilePath(dir, idx) contains, err := b.fileContainsObject(path, addr) if err != nil { return 0, err } if contains { return idx, nil } } return 0, logicerr.Wrap(new(apistatus.ObjectNotFound)) } func (b *BlobTree) fileContainsObject(path string, addr oid.Address) (bool, error) { b.fileLock.RLock(path) defer b.fileLock.RUnlock(path) records, err := b.readFileContent(path) if err != nil { return false, err } for i := range records { if records[i].Address.Equals(addr) { return true, nil } } return false, nil }