package blobovniczatree import ( "context" "errors" "path/filepath" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" "go.etcd.io/bbolt" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) // Put saves object in the maximum weight blobobnicza. // // returns error if could not save object in any blobovnicza. func (b *Blobovniczas) Put(ctx context.Context, prm common.PutPrm) (common.PutRes, error) { _, span := tracing.StartSpanFromContext(ctx, "Blobovniczas.Put", trace.WithAttributes( attribute.String("address", prm.Address.EncodeToString()), attribute.Bool("dont_compress", prm.DontCompress), )) defer span.End() if b.readOnly { return common.PutRes{}, common.ErrReadOnly } if !prm.DontCompress { prm.RawData = b.compression.Compress(prm.RawData) } var putPrm blobovnicza.PutPrm putPrm.SetAddress(prm.Address) putPrm.SetMarshaledObject(prm.RawData) it := &putIterator{ B: b, ID: nil, AllFull: true, PutPrm: putPrm, } if err := b.iterateDeepest(ctx, prm.Address, func(s string) (bool, error) { return it.iterate(ctx, s) }); err != nil { return common.PutRes{}, err } else if it.ID == nil { if it.AllFull { return common.PutRes{}, common.ErrNoSpace } return common.PutRes{}, errPutFailed } return common.PutRes{StorageID: it.ID.Bytes()}, nil } type putIterator struct { B *Blobovniczas ID *blobovnicza.ID AllFull bool PutPrm blobovnicza.PutPrm } func (i *putIterator) iterate(ctx context.Context, path string) (bool, error) { active, err := i.B.getActivated(path) if err != nil { if !isLogical(err) { i.B.reportError(logs.BlobovniczatreeCouldNotGetActiveBlobovnicza, err) } else { i.B.log.Debug(logs.BlobovniczatreeCouldNotGetActiveBlobovnicza, zap.String("error", err.Error())) } return false, nil } if _, err := active.blz.Put(ctx, i.PutPrm); err != nil { // Check if blobovnicza is full. We could either receive `blobovnicza.ErrFull` error // or update active blobovnicza in other thread. In the latter case the database will be closed // and `updateActive` takes care of not updating the active blobovnicza twice. if isFull := errors.Is(err, blobovnicza.ErrFull); isFull || errors.Is(err, bbolt.ErrDatabaseNotOpen) { if isFull { i.B.log.Debug(logs.BlobovniczatreeBlobovniczaOverflowed, zap.String("path", filepath.Join(path, u64ToHexString(active.ind)))) } if err := i.B.updateActive(path, &active.ind); err != nil { if !isLogical(err) { i.B.reportError(logs.BlobovniczatreeCouldNotUpdateActiveBlobovnicza, err) } else { i.B.log.Debug(logs.BlobovniczatreeCouldNotUpdateActiveBlobovnicza, zap.String("level", path), zap.String("error", err.Error())) } return false, nil } return i.iterate(ctx, path) } i.AllFull = false if !isLogical(err) { i.B.reportError(logs.BlobovniczatreeCouldNotPutObjectToActiveBlobovnicza, err) } else { i.B.log.Debug(logs.BlobovniczatreeCouldNotPutObjectToActiveBlobovnicza, zap.String("path", filepath.Join(path, u64ToHexString(active.ind))), zap.String("error", err.Error())) } return false, nil } path = filepath.Join(path, u64ToHexString(active.ind)) i.ID = blobovnicza.NewIDFromBytes([]byte(path)) return true, nil }