diff --git a/pkg/services/object/delete/prm.go b/pkg/services/object/delete/prm.go new file mode 100644 index 000000000..57fa52f39 --- /dev/null +++ b/pkg/services/object/delete/prm.go @@ -0,0 +1,28 @@ +package deletesvc + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-node/pkg/services/object/util" +) + +type Prm struct { + common *util.CommonPrm + + addr *object.Address +} + +func (p *Prm) WithCommonPrm(v *util.CommonPrm) *Prm { + if p != nil { + p.common = v + } + + return p +} + +func (p *Prm) WithAddress(v *object.Address) *Prm { + if p != nil { + p.addr = v + } + + return p +} diff --git a/pkg/services/object/delete/res.go b/pkg/services/object/delete/res.go new file mode 100644 index 000000000..db1b34907 --- /dev/null +++ b/pkg/services/object/delete/res.go @@ -0,0 +1,3 @@ +package deletesvc + +type Response struct{} diff --git a/pkg/services/object/delete/service.go b/pkg/services/object/delete/service.go new file mode 100644 index 000000000..593c51677 --- /dev/null +++ b/pkg/services/object/delete/service.go @@ -0,0 +1,126 @@ +package deletesvc + +import ( + "context" + + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head" + putsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/put" + objutil "github.com/nspcc-dev/neofs-node/pkg/services/object/util" + "github.com/pkg/errors" +) + +type Service struct { + *cfg +} + +type Option func(*cfg) + +type cfg struct { + ownerID *owner.ID + + keyStorage *objutil.KeyStorage + + putSvc *putsvc.Service + + headSvc *headsvc.Service +} + +func defaultCfg() *cfg { + return new(cfg) +} + +func NewService(opts ...Option) *Service { + c := defaultCfg() + + for i := range opts { + opts[i](c) + } + + return &Service{ + cfg: c, + } +} + +func (s *Service) Delete(ctx context.Context, prm *Prm) (*Response, error) { + ownerID := s.ownerID + if token := prm.common.SessionToken(); token != nil { + ownerID = token.OwnerID() + } + + if ownerID == nil { + return nil, errors.Errorf("(%T) missing owner identifier", s) + } + + tool := &deleteTool{ + ctx: ctx, + putSvc: s.putSvc, + obj: newTombstone(ownerID, prm.addr.GetContainerID()), + addr: prm.addr, + commonPrm: prm.common, + } + + if err := s.deleteAll(tool); err != nil { + return nil, errors.Wrapf(err, "(%T) could not delete all objcet relations", s) + } + + return new(Response), nil +} + +func (s *Service) deleteAll(tool *deleteTool) error { + headResult, err := s.headSvc.Head(tool.ctx, new(headsvc.Prm). + WithAddress(tool.addr). + WithCommonPrm(tool.commonPrm), + ) + if err != nil { + return errors.Wrapf(err, "(%T) could not receive Head result", s) + } + + hdr := headResult.Header() + + if err := tool.delete(hdr.GetID()); err != nil { + return errors.Wrapf(err, "(%T) could not remove object", s) + } + + prevID := hdr.GetPreviousID() + + if rightChild := headResult.RightChild(); rightChild != nil { + if err := tool.delete(rightChild.GetID()); err != nil { + return errors.Wrapf(err, "(%T) could not remove right child", s) + } + + prevID = rightChild.GetPreviousID() + } + + if prevID != nil { + tool.addr.SetObjectID(prevID) + + return s.deleteAll(tool) + } + + return nil +} + +func WithOwnerID(v *owner.ID) Option { + return func(c *cfg) { + c.ownerID = v + } +} + +func WithKeyStorage(v *objutil.KeyStorage) Option { + return func(c *cfg) { + c.keyStorage = v + } +} + +func WithPutService(v *putsvc.Service) Option { + return func(c *cfg) { + c.putSvc = v + } +} + +func WitHeadService(v *headsvc.Service) Option { + return func(c *cfg) { + c.headSvc = v + } +} diff --git a/pkg/services/object/delete/util.go b/pkg/services/object/delete/util.go new file mode 100644 index 000000000..564560347 --- /dev/null +++ b/pkg/services/object/delete/util.go @@ -0,0 +1,71 @@ +package deletesvc + +import ( + "context" + + "github.com/nspcc-dev/neofs-api-go/pkg/container" + objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-node/pkg/core/object" + putsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/put" + "github.com/nspcc-dev/neofs-node/pkg/services/object/util" + "github.com/pkg/errors" +) + +type deleteTool struct { + ctx context.Context + + putSvc *putsvc.Service + + obj *object.RawObject + + addr *objectSDK.Address + + commonPrm *util.CommonPrm +} + +func newTombstone(ownerID *owner.ID, cid *container.ID) *object.RawObject { + obj := object.NewRaw() + obj.SetContainerID(cid) + obj.SetOwnerID(ownerID) + obj.SetType(objectSDK.TypeTombstone) + + return obj +} + +func (d *deleteTool) delete(id *objectSDK.ID) error { + d.addr.SetObjectID(id) + + // FIXME: implement marshaler + addrBytes, err := d.addr.ToV2().StableMarshal(nil) + if err != nil { + return errors.Wrapf(err, "(%T) could not marshal address", d) + } + + r, err := d.putSvc.Put(d.ctx) + if err != nil { + return errors.Wrapf(err, "(%T) could not open put stream", d) + } + + d.obj.SetID(id) + d.obj.SetPayload(addrBytes) + + if err := r.Init(new(putsvc.PutInitPrm). + WithObject(d.obj). + WithCommonPrm(d.commonPrm), + ); err != nil { + return errors.Wrapf(err, "(%T) could not initialize tombstone stream", d) + } + + if err := r.SendChunk(new(putsvc.PutChunkPrm). + WithChunk(addrBytes), + ); err != nil { + return errors.Wrapf(err, "(%T) could not send tombstone payload", d) + } + + if _, err := r.Close(); err != nil { + return errors.Wrapf(err, "(%T) could not close tombstone stream", d) + } + + return nil +} diff --git a/pkg/services/object/delete/v2/service.go b/pkg/services/object/delete/v2/service.go new file mode 100644 index 000000000..3e3bbb676 --- /dev/null +++ b/pkg/services/object/delete/v2/service.go @@ -0,0 +1,50 @@ +package deletesvc + +import ( + "context" + + objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" + deletesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/delete" + "github.com/pkg/errors" +) + +// Service implements Delete operation of Object service v2. +type Service struct { + *cfg +} + +// Option represents Service constructor option. +type Option func(*cfg) + +type cfg struct { + svc *deletesvc.Service +} + +// NewService constructs Service instance from provided options. +func NewService(opts ...Option) *Service { + c := new(cfg) + + for i := range opts { + opts[i](c) + } + + return &Service{ + cfg: c, + } +} + +// Delete calls internal service. +func (s *Service) Delete(ctx context.Context, req *objectV2.DeleteRequest) (*objectV2.DeleteResponse, error) { + r, err := s.svc.Delete(ctx, toPrm(req)) + if err != nil { + return nil, errors.Wrapf(err, "(%T) could not get object header", s) + } + + return fromResponse(r), nil +} + +func WithInternalService(v *deletesvc.Service) Option { + return func(c *cfg) { + c.svc = v + } +} diff --git a/pkg/services/object/delete/v2/util.go b/pkg/services/object/delete/v2/util.go new file mode 100644 index 000000000..6cf0733cc --- /dev/null +++ b/pkg/services/object/delete/v2/util.go @@ -0,0 +1,22 @@ +package deletesvc + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg/object" + objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" + deletesvc "github.com/nspcc-dev/neofs-node/pkg/services/object/delete" + "github.com/nspcc-dev/neofs-node/pkg/services/object/util" +) + +func toPrm(req *objectV2.DeleteRequest) *deletesvc.Prm { + body := req.GetBody() + + return new(deletesvc.Prm). + WithAddress( + object.NewAddressFromV2(body.GetAddress()), + ). + WithCommonPrm(util.CommonPrmFromV2(req)) +} + +func fromResponse(r *deletesvc.Response) *objectV2.DeleteResponse { + return new(objectV2.DeleteResponse) +}