container: Add ListStream method #291
14 changed files with 626 additions and 0 deletions
BIN
api/apemanager/grpc/service_grpc.pb.go
generated
BIN
api/apemanager/grpc/service_grpc.pb.go
generated
Binary file not shown.
|
@ -762,3 +762,138 @@ func (r *ListResponse) FromGRPCMessage(m grpc.Message) error {
|
|||
|
||||
return r.ResponseHeaders.FromMessage(v)
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) ToGRPCMessage() grpc.Message {
|
||||
var m *container.ListStreamRequest_Body
|
||||
|
||||
if r != nil {
|
||||
m = new(container.ListStreamRequest_Body)
|
||||
|
||||
m.SetOwnerId(r.ownerID.ToGRPCMessage().(*refsGRPC.OwnerID))
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) FromGRPCMessage(m grpc.Message) error {
|
||||
v, ok := m.(*container.ListStreamRequest_Body)
|
||||
if !ok {
|
||||
return message.NewUnexpectedMessageType(m, v)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
ownerID := v.GetOwnerId()
|
||||
if ownerID == nil {
|
||||
r.ownerID = nil
|
||||
} else {
|
||||
if r.ownerID == nil {
|
||||
r.ownerID = new(refs.OwnerID)
|
||||
}
|
||||
|
||||
err = r.ownerID.FromGRPCMessage(ownerID)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *ListStreamRequest) ToGRPCMessage() grpc.Message {
|
||||
var m *container.ListStreamRequest
|
||||
|
||||
if r != nil {
|
||||
m = new(container.ListStreamRequest)
|
||||
|
||||
m.SetBody(r.body.ToGRPCMessage().(*container.ListStreamRequest_Body))
|
||||
r.RequestHeaders.ToMessage(m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *ListStreamRequest) FromGRPCMessage(m grpc.Message) error {
|
||||
v, ok := m.(*container.ListStreamRequest)
|
||||
if !ok {
|
||||
return message.NewUnexpectedMessageType(m, v)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
body := v.GetBody()
|
||||
if body == nil {
|
||||
r.body = nil
|
||||
} else {
|
||||
if r.body == nil {
|
||||
r.body = new(ListStreamRequestBody)
|
||||
}
|
||||
|
||||
err = r.body.FromGRPCMessage(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return r.RequestHeaders.FromMessage(v)
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) ToGRPCMessage() grpc.Message {
|
||||
var m *container.ListStreamResponse_Body
|
||||
|
||||
if r != nil {
|
||||
m = new(container.ListStreamResponse_Body)
|
||||
|
||||
m.SetContainerIds(refs.ContainerIDsToGRPCMessage(r.cidList))
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) FromGRPCMessage(m grpc.Message) error {
|
||||
v, ok := m.(*container.ListStreamResponse_Body)
|
||||
if !ok {
|
||||
return message.NewUnexpectedMessageType(m, v)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
r.cidList, err = refs.ContainerIDsFromGRPCMessage(v.GetContainerIds())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *ListStreamResponse) ToGRPCMessage() grpc.Message {
|
||||
var m *container.ListStreamResponse
|
||||
|
||||
if r != nil {
|
||||
m = new(container.ListStreamResponse)
|
||||
|
||||
m.SetBody(r.body.ToGRPCMessage().(*container.ListStreamResponse_Body))
|
||||
r.ResponseHeaders.ToMessage(m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *ListStreamResponse) FromGRPCMessage(m grpc.Message) error {
|
||||
v, ok := m.(*container.ListStreamResponse)
|
||||
if !ok {
|
||||
return message.NewUnexpectedMessageType(m, v)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
body := v.GetBody()
|
||||
if body == nil {
|
||||
r.body = nil
|
||||
} else {
|
||||
if r.body == nil {
|
||||
r.body = new(ListStreamResponseBody)
|
||||
}
|
||||
|
||||
err = r.body.FromGRPCMessage(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return r.ResponseHeaders.FromMessage(v)
|
||||
}
|
||||
|
|
BIN
api/container/grpc/service_frostfs.pb.go
generated
BIN
api/container/grpc/service_frostfs.pb.go
generated
Binary file not shown.
|
@ -157,3 +157,41 @@ func DoFuzzJSONListResponse(data []byte) int {
|
|||
}
|
||||
return 1
|
||||
}
|
||||
func DoFuzzProtoListStreamRequest(data []byte) int {
|
||||
msg := new(ListStreamRequest)
|
||||
if err := msg.UnmarshalProtobuf(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
_ = msg.MarshalProtobuf(nil)
|
||||
return 1
|
||||
}
|
||||
func DoFuzzJSONListStreamRequest(data []byte) int {
|
||||
msg := new(ListStreamRequest)
|
||||
if err := msg.UnmarshalJSON(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
_, err := msg.MarshalJSON()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
func DoFuzzProtoListStreamResponse(data []byte) int {
|
||||
msg := new(ListStreamResponse)
|
||||
if err := msg.UnmarshalProtobuf(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
_ = msg.MarshalProtobuf(nil)
|
||||
return 1
|
||||
}
|
||||
func DoFuzzJSONListStreamResponse(data []byte) int {
|
||||
msg := new(ListStreamResponse)
|
||||
if err := msg.UnmarshalJSON(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
_, err := msg.MarshalJSON()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
|
|
@ -89,3 +89,23 @@ func FuzzJSONListResponse(f *testing.F) {
|
|||
DoFuzzJSONListResponse(data)
|
||||
})
|
||||
}
|
||||
func FuzzProtoListStreamRequest(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
DoFuzzProtoListStreamRequest(data)
|
||||
})
|
||||
}
|
||||
func FuzzJSONListStreamRequest(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
DoFuzzJSONListStreamRequest(data)
|
||||
})
|
||||
}
|
||||
func FuzzProtoListStreamResponse(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
DoFuzzProtoListStreamResponse(data)
|
||||
})
|
||||
}
|
||||
func FuzzJSONListStreamResponse(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
DoFuzzJSONListStreamResponse(data)
|
||||
})
|
||||
}
|
||||
|
|
BIN
api/container/grpc/service_grpc.pb.go
generated
BIN
api/container/grpc/service_grpc.pb.go
generated
Binary file not shown.
|
@ -343,3 +343,65 @@ func (r *ListResponseBody) StableSize() (size int) {
|
|||
func (r *ListResponseBody) Unmarshal(data []byte) error {
|
||||
return message.Unmarshal(r, data, new(container.ListResponse_Body))
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) StableMarshal(buf []byte) []byte {
|
||||
if r == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
if buf == nil {
|
||||
buf = make([]byte, r.StableSize())
|
||||
}
|
||||
|
||||
protoutil.NestedStructureMarshal(listReqBodyOwnerField, buf, r.ownerID)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) StableSize() (size int) {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
size += protoutil.NestedStructureSize(listReqBodyOwnerField, r.ownerID)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) Unmarshal(data []byte) error {
|
||||
return message.Unmarshal(r, data, new(container.ListStreamRequest_Body))
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) StableMarshal(buf []byte) []byte {
|
||||
if r == nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
if buf == nil {
|
||||
buf = make([]byte, r.StableSize())
|
||||
}
|
||||
|
||||
var offset int
|
||||
|
||||
for i := range r.cidList {
|
||||
offset += protoutil.NestedStructureMarshal(listRespBodyIDsField, buf[offset:], &r.cidList[i])
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) StableSize() (size int) {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
for i := range r.cidList {
|
||||
size += protoutil.NestedStructureSize(listRespBodyIDsField, &r.cidList[i])
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) Unmarshal(data []byte) error {
|
||||
return message.Unmarshal(r, data, new(container.ListStreamResponse_Body))
|
||||
}
|
||||
|
|
|
@ -109,6 +109,26 @@ type ListResponse struct {
|
|||
session.ResponseHeaders
|
||||
}
|
||||
|
||||
type ListStreamRequestBody struct {
|
||||
ownerID *refs.OwnerID
|
||||
}
|
||||
|
||||
type ListStreamRequest struct {
|
||||
body *ListStreamRequestBody
|
||||
|
||||
session.RequestHeaders
|
||||
}
|
||||
|
||||
type ListStreamResponseBody struct {
|
||||
cidList []refs.ContainerID
|
||||
}
|
||||
|
||||
type ListStreamResponse struct {
|
||||
body *ListStreamResponseBody
|
||||
|
||||
session.ResponseHeaders
|
||||
}
|
||||
|
||||
func (a *Attribute) GetKey() string {
|
||||
if a != nil {
|
||||
return a.key
|
||||
|
@ -444,3 +464,51 @@ func (r *ListResponse) GetBody() *ListResponseBody {
|
|||
func (r *ListResponse) SetBody(v *ListResponseBody) {
|
||||
r.body = v
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) GetOwnerID() *refs.OwnerID {
|
||||
if r != nil {
|
||||
return r.ownerID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ListStreamRequestBody) SetOwnerID(v *refs.OwnerID) {
|
||||
r.ownerID = v
|
||||
}
|
||||
|
||||
func (r *ListStreamRequest) GetBody() *ListStreamRequestBody {
|
||||
if r != nil {
|
||||
return r.body
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ListStreamRequest) SetBody(v *ListStreamRequestBody) {
|
||||
r.body = v
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) GetContainerIDs() []refs.ContainerID {
|
||||
if r != nil {
|
||||
return r.cidList
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseBody) SetContainerIDs(v []refs.ContainerID) {
|
||||
r.cidList = v
|
||||
}
|
||||
|
||||
func (r *ListStreamResponse) GetBody() *ListStreamResponseBody {
|
||||
if r != nil {
|
||||
return r.body
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ListStreamResponse) SetBody(v *ListStreamResponseBody) {
|
||||
r.body = v
|
||||
}
|
||||
|
|
BIN
api/object/grpc/service_grpc.pb.go
generated
BIN
api/object/grpc/service_grpc.pb.go
generated
Binary file not shown.
|
@ -13,6 +13,7 @@ const (
|
|||
rpcContainerGet = "Get"
|
||||
rpcContainerDel = "Delete"
|
||||
rpcContainerList = "List"
|
||||
rpcContainerStream = "ListStream"
|
||||
rpcContainerGetEACL = "GetExtendedACL"
|
||||
rpcContainerUsedSpace = "AnnounceUsedSpace"
|
||||
)
|
||||
|
@ -80,3 +81,27 @@ func ListContainers(
|
|||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type ListStreamResponseReader struct {
|
||||
r client.MessageReader
|
||||
}
|
||||
|
||||
func (r *ListStreamResponseReader) Read(resp *container.ListStreamResponse) error {
|
||||
return r.r.ReadMessage(resp)
|
||||
}
|
||||
|
||||
// ListContainersStream executes ContainerService.ListStream RPC.
|
||||
func ListContainersStream(
|
||||
cli *client.Client,
|
||||
req *container.ListStreamRequest,
|
||||
opts ...client.CallOption,
|
||||
) (*ListStreamResponseReader, error) {
|
||||
wc, err := client.OpenServerStream(cli, common.CallMethodInfoServerStream(serviceContainer, rpcContainerStream), req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ListStreamResponseReader{
|
||||
r: wc,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -46,6 +46,10 @@ func serviceMessageBody(req any) stableMarshaler {
|
|||
return v.GetBody()
|
||||
case *container.ListResponse:
|
||||
return v.GetBody()
|
||||
case *container.ListStreamRequest:
|
||||
return v.GetBody()
|
||||
case *container.ListStreamResponse:
|
||||
return v.GetBody()
|
||||
|
||||
/* Object */
|
||||
case *object.PutRequest:
|
||||
|
|
182
client/container_list_stream.go
Normal file
182
client/container_list_stream.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/signature"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
)
|
||||
|
||||
// PrmContainerListStream groups parameters of ContainerListStream operation.
|
||||
type PrmContainerListStream struct {
|
||||
XHeaders []string
|
||||
|
||||
Account user.ID
|
||||
|
||||
|
||||
dstepanov-yadro
commented
Maybe Maybe `Account` -> `OwnerID`?
|
||||
Session *session.Container
|
||||
}
|
||||
|
||||
// SetAccount sets identifier of the FrostFS account to list the containers.
|
||||
// Required parameter.
|
||||
//
|
||||
// Deprecated: Use PrmContainerListStream.Account instead.
|
||||
func (x *PrmContainerListStream) SetAccount(id user.ID) {
|
||||
aarifullin
commented
You can't get deprecated the method that has never been used as it's new :) So, you don't need this setter You can't get deprecated the method that has never been used as it's new :) So, you don't need this setter
|
||||
x.Account = id
|
||||
}
|
||||
|
||||
func (x *PrmContainerListStream) buildRequest(c *Client) (*container.ListStreamRequest, error) {
|
||||
if x.Account.IsEmpty() {
|
||||
return nil, errorAccountNotSet
|
||||
}
|
||||
|
||||
var ownerV2 refs.OwnerID
|
||||
x.Account.WriteToV2(&ownerV2)
|
||||
|
||||
reqBody := new(container.ListStreamRequestBody)
|
||||
reqBody.SetOwnerID(&ownerV2)
|
||||
|
||||
var meta v2session.RequestMetaHeader
|
||||
writeXHeadersToMeta(x.XHeaders, &meta)
|
||||
|
||||
if x.Session != nil {
|
||||
var tokv2 v2session.Token
|
||||
x.Session.WriteToV2(&tokv2)
|
||||
|
||||
meta.SetSessionToken(&tokv2)
|
||||
}
|
||||
|
||||
var req container.ListStreamRequest
|
||||
req.SetBody(reqBody)
|
||||
c.prepareRequest(&req, &meta)
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
type ResContainerListStream struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
type ContainerListReader struct {
|
||||
client *Client
|
||||
cancelCtxStream context.CancelFunc
|
||||
err error
|
||||
res ResContainerListStream
|
||||
stream interface {
|
||||
Read(resp *container.ListStreamResponse) error
|
||||
}
|
||||
tail []refs.ContainerID
|
||||
}
|
||||
|
||||
func (x *ContainerListReader) Read(buf []cid.ID) (int, bool) {
|
||||
dstepanov-yadro
commented
`int` is count of read containers. What is `bool`?
|
||||
if len(buf) == 0 {
|
||||
panic("empty buffer in ContainerListReader.ReadList")
|
||||
aarifullin
commented
`Read` instead of `Read.List` ? :)
To be honest, this panic looks unnecessarily. You can define
```go
var errEmptyBuffer = errors.New("empty read buffer")
```
```go
if len(buf) == 0 {
x.err = errEmptyBuffer
return 0, false
}
```
|
||||
}
|
||||
|
||||
read := copyCnrIDBuffers(buf, x.tail)
|
||||
x.tail = x.tail[read:]
|
||||
|
||||
if len(buf) == read {
|
||||
return read, true
|
||||
}
|
||||
|
||||
for {
|
||||
var resp container.ListStreamResponse
|
||||
x.err = x.stream.Read(&resp)
|
||||
if x.err != nil {
|
||||
return read, false
|
||||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
return read, false
|
||||
}
|
||||
|
||||
// read new chunk of objects
|
||||
ids := resp.GetBody().GetContainerIDs()
|
||||
if len(ids) == 0 {
|
||||
// just skip empty lists since they are not prohibited by protocol
|
||||
continue
|
||||
}
|
||||
|
||||
ln := copyCnrIDBuffers(buf[read:], ids)
|
||||
read += ln
|
||||
|
||||
if read == len(buf) {
|
||||
// save the tail
|
||||
x.tail = append(x.tail, ids[ln:]...)
|
||||
aarifullin
commented
Optional changeThat's barely possible for listing containers but if read buffer is quite small and stream is going to send a big number of containers then such accumulation in the tail may lead to overflow.
P.S. about small buffer: ### Optional change
That's barely possible for listing containers but if read buffer is quite small and stream is going to send a big number of containers then such accumulation in the tail may lead to overflow.
Didn't you consider?
```go
if len(x.tail)+len(remainingIDs) > x.maxTailSize {
x.err = errors.New("internal buffer overflow")
return 0, false
}
```
P.S. about small buffer: `Iterate` already uses such buffer
|
||||
|
||||
return read, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copyCnrIDBuffers(dst []cid.ID, src []refs.ContainerID) int {
|
||||
var i int
|
||||
for ; i < len(dst) && i < len(src); i++ {
|
||||
_ = dst[i].ReadFromV2(src[i])
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (x *ContainerListReader) Iterate(f func(cid.ID) bool) error {
|
||||
buf := make([]cid.ID, 1)
|
||||
|
||||
for {
|
||||
// Do not check first return value because `len(buf) == 1`,
|
||||
// so false means nothing was read.
|
||||
_, ok := x.Read(buf)
|
||||
if !ok {
|
||||
res, err := x.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apistatus.ErrFromStatus(res.Status())
|
||||
}
|
||||
if f(buf[0]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ContainerListReader) Close() (*ResContainerListStream, error) {
|
||||
defer x.cancelCtxStream()
|
||||
|
||||
if x.err != nil && !errors.Is(x.err, io.EOF) {
|
||||
return nil, x.err
|
||||
}
|
||||
|
||||
return &x.res, nil
|
||||
}
|
||||
|
||||
func (c *Client) ContainerListInit(ctx context.Context, prm PrmContainerListStream) (*ContainerListReader, error) {
|
||||
req, err := prm.buildRequest(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = signature.SignServiceMessage(&c.prm.Key, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
||||
var r ContainerListReader
|
||||
ctx, r.cancelCtxStream = context.WithCancel(ctx)
|
||||
|
||||
r.stream, err = rpcapi.ListContainersStream(&c.c, req, client.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open stream: %w", err)
|
||||
}
|
||||
r.client = c
|
||||
|
||||
return &r, nil
|
||||
}
|
|
@ -104,6 +104,10 @@ func (m *mockClient) containerList(context.Context, PrmContainerList) ([]cid.ID,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockClient) containerListStream(context.Context, PrmListStream) (ResListStream, error) {
|
||||
return ResListStream{}, nil
|
||||
}
|
||||
|
||||
func (m *mockClient) containerDelete(context.Context, PrmContainerDelete) error {
|
||||
return nil
|
||||
}
|
||||
|
|
88
pool/pool.go
88
pool/pool.go
|
@ -47,6 +47,8 @@ type client interface {
|
|||
containerGet(context.Context, PrmContainerGet) (container.Container, error)
|
||||
// see clientWrapper.containerList.
|
||||
containerList(context.Context, PrmContainerList) ([]cid.ID, error)
|
||||
// see clientWrapper.containerListStream.
|
||||
containerListStream(context.Context, PrmListStream) (ResListStream, error)
|
||||
// see clientWrapper.containerDelete.
|
||||
containerDelete(context.Context, PrmContainerDelete) error
|
||||
// see clientWrapper.apeManagerAddChain.
|
||||
|
@ -145,6 +147,7 @@ const (
|
|||
methodContainerPut
|
||||
methodContainerGet
|
||||
methodContainerList
|
||||
methodContainerListStream
|
||||
methodContainerDelete
|
||||
methodEndpointInfo
|
||||
methodNetworkInfo
|
||||
|
@ -529,6 +532,75 @@ func (c *clientWrapper) containerList(ctx context.Context, prm PrmContainerList)
|
|||
return res.Containers(), nil
|
||||
}
|
||||
|
||||
// PrmListStream groups parameters of ListContainersStream operation.
|
||||
type PrmListStream struct {
|
||||
OwnerID user.ID
|
||||
|
||||
Session *session.Container
|
||||
}
|
||||
|
||||
// ResListStream is designed to read list of object identifiers from FrostFS system.
|
||||
//
|
||||
// Must be initialized using Pool.ListContainersStream, any other usage is unsafe.
|
||||
type ResListStream struct {
|
||||
r *sdkClient.ContainerListReader
|
||||
handleError func(context.Context, apistatus.Status, error) error
|
||||
}
|
||||
|
||||
// Read reads another list of the container identifiers.
|
||||
func (x *ResListStream) Read(buf []cid.ID) (int, error) {
|
||||
n, ok := x.r.Read(buf)
|
||||
if !ok {
|
||||
res, err := x.r.Close()
|
||||
if err == nil {
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
var status apistatus.Status
|
||||
if res != nil {
|
||||
status = res.Status()
|
||||
}
|
||||
err = x.handleError(nil, status, err)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Iterate iterates over the list of found container identifiers.
|
||||
// f can return true to stop iteration earlier.
|
||||
//
|
||||
// Returns an error if container can't be read.
|
||||
func (x *ResListStream) Iterate(f func(cid.ID) bool) error {
|
||||
return x.r.Iterate(f)
|
||||
}
|
||||
|
||||
// Close ends reading list of the matched containers and returns the result of the operation
|
||||
// along with the final results. Must be called after using the ResListStream.
|
||||
func (x *ResListStream) Close() {
|
||||
_, _ = x.r.Close()
|
||||
}
|
||||
|
||||
// containerList invokes sdkClient.ContainerList parse response status to error and return result as is.
|
||||
func (c *clientWrapper) containerListStream(ctx context.Context, prm PrmListStream) (ResListStream, error) {
|
||||
cl, err := c.getClient()
|
||||
if err != nil {
|
||||
return ResListStream{}, err
|
||||
}
|
||||
|
||||
cliPrm := sdkClient.PrmContainerListStream{
|
||||
Account: prm.OwnerID,
|
||||
Session: prm.Session,
|
||||
}
|
||||
|
||||
res, err := cl.ContainerListInit(ctx, cliPrm)
|
||||
if err = c.handleError(ctx, nil, err); err != nil {
|
||||
return ResListStream{}, fmt.Errorf("init container listing on client: %w", err)
|
||||
}
|
||||
return ResListStream{r: res, handleError: c.handleError}, nil
|
||||
}
|
||||
|
||||
// containerDelete invokes sdkClient.ContainerDelete parse response status to error.
|
||||
// It also waits for the container to be removed from the network.
|
||||
func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDelete) error {
|
||||
|
@ -2887,6 +2959,22 @@ func (p *Pool) ListContainers(ctx context.Context, prm PrmContainerList) ([]cid.
|
|||
return cnrIDs, nil
|
||||
}
|
||||
|
||||
// // ListContainers requests identifiers of the account-owned containers.
|
||||
func (p *Pool) ListContainersStream(ctx context.Context, prm PrmListStream) (ResListStream, error) {
|
||||
var res ResListStream
|
||||
cp, err := p.connection()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res, err = cp.containerListStream(ctx, prm)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("list containers stream via client '%s': %w", cp.address(), err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// DeleteContainer sends request to remove the FrostFS container and waits for the operation to complete.
|
||||
//
|
||||
// Waiting parameters can be specified using SetWaitParams. If not called, defaults are used:
|
||||
|
|
Loading…
Reference in a new issue
Please, leave a comment how this parameter is used :)