git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/client/object_search.go (about) 1 package client 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "errors" 7 "fmt" 8 "io" 9 10 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" 11 v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" 12 v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" 13 rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" 14 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" 15 v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" 16 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" 17 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" 18 apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" 19 cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" 20 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" 21 oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" 22 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" 23 ) 24 25 // PrmObjectSearch groups parameters of ObjectSearch operation. 26 type PrmObjectSearch struct { 27 XHeaders []string 28 29 Local bool 30 31 BearerToken *bearer.Token 32 33 Session *session.Object 34 35 ContainerID *cid.ID 36 37 Key *ecdsa.PrivateKey 38 39 Filters object.SearchFilters 40 } 41 42 // MarkLocal tells the server to execute the operation locally. 43 // 44 // Deprecated: Use PrmObjectSearch.Local instead. 45 func (x *PrmObjectSearch) MarkLocal() { 46 x.Local = true 47 } 48 49 // WithinSession specifies session within which the search query must be executed. 50 // 51 // Creator of the session acquires the authorship of the request. 52 // This may affect the execution of an operation (e.g. access control). 53 // 54 // Must be signed. 55 // 56 // Deprecated: Use PrmObjectSearch.Session instead. 57 func (x *PrmObjectSearch) WithinSession(t session.Object) { 58 x.Session = &t 59 } 60 61 // WithBearerToken attaches bearer token to be used for the operation. 62 // 63 // If set, underlying eACL rules will be used in access control. 64 // 65 // Must be signed. 66 // 67 // Deprecated: Use PrmObjectSearch.BearerToken instead. 68 func (x *PrmObjectSearch) WithBearerToken(t bearer.Token) { 69 x.BearerToken = &t 70 } 71 72 // WithXHeaders specifies list of extended headers (string key-value pairs) 73 // to be attached to the request. Must have an even length. 74 // 75 // Slice must not be mutated until the operation completes. 76 // 77 // Deprecated: Use PrmObjectSearch.XHeaders instead. 78 func (x *PrmObjectSearch) WithXHeaders(hs ...string) { 79 x.XHeaders = hs 80 } 81 82 // UseKey specifies private key to sign the requests. 83 // If key is not provided, then Client default key is used. 84 // 85 // Deprecated: Use PrmObjectSearch.Key instead. 86 func (x *PrmObjectSearch) UseKey(key ecdsa.PrivateKey) { 87 x.Key = &key 88 } 89 90 // InContainer specifies the container in which to look for objects. 91 // Required parameter. 92 // 93 // Deprecated: Use PrmObjectSearch.ContainerID instead. 94 func (x *PrmObjectSearch) InContainer(id cid.ID) { 95 x.ContainerID = &id 96 } 97 98 // SetFilters sets filters by which to select objects. All container objects 99 // match unset/empty filters. 100 // 101 // Deprecated: Use PrmObjectSearch.Filters instead. 102 func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) { 103 x.Filters = filters 104 } 105 106 // ResObjectSearch groups the final result values of ObjectSearch operation. 107 type ResObjectSearch struct { 108 statusRes 109 } 110 111 // ObjectListReader is designed to read list of object identifiers from FrostFS system. 112 // 113 // Must be initialized using Client.ObjectSearch, any other usage is unsafe. 114 type ObjectListReader struct { 115 client *Client 116 cancelCtxStream context.CancelFunc 117 err error 118 res ResObjectSearch 119 stream interface { 120 Read(resp *v2object.SearchResponse) error 121 } 122 tail []v2refs.ObjectID 123 } 124 125 // Read reads another list of the object identifiers. Works similar to 126 // io.Reader.Read but copies oid.ID and returns success flag instead of error. 127 // 128 // Failure reason can be received via Close. 129 // 130 // Panics if buf has zero length. 131 func (x *ObjectListReader) Read(buf []oid.ID) (int, bool) { 132 if len(buf) == 0 { 133 panic("empty buffer in ObjectListReader.ReadList") 134 } 135 136 read := copyIDBuffers(buf, x.tail) 137 x.tail = x.tail[read:] 138 139 if len(buf) == read { 140 return read, true 141 } 142 143 for { 144 var resp v2object.SearchResponse 145 x.err = x.stream.Read(&resp) 146 if x.err != nil { 147 return read, false 148 } 149 150 x.res.st, x.err = x.client.processResponse(&resp) 151 if x.err != nil || !apistatus.IsSuccessful(x.res.st) { 152 return read, false 153 } 154 155 // read new chunk of objects 156 ids := resp.GetBody().GetIDList() 157 if len(ids) == 0 { 158 // just skip empty lists since they are not prohibited by protocol 159 continue 160 } 161 162 ln := copyIDBuffers(buf[read:], ids) 163 read += ln 164 165 if read == len(buf) { 166 // save the tail 167 x.tail = append(x.tail, ids[ln:]...) 168 169 return read, true 170 } 171 } 172 } 173 174 func copyIDBuffers(dst []oid.ID, src []v2refs.ObjectID) int { 175 var i int 176 for ; i < len(dst) && i < len(src); i++ { 177 _ = dst[i].ReadFromV2(src[i]) 178 } 179 return i 180 } 181 182 // Iterate iterates over the list of found object identifiers. 183 // f can return true to stop iteration earlier. 184 // 185 // Returns an error if object can't be read. 186 func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error { 187 buf := make([]oid.ID, 1) 188 189 for { 190 // Do not check first return value because `len(buf) == 1`, 191 // so false means nothing was read. 192 _, ok := x.Read(buf) 193 if !ok { 194 res, err := x.Close() 195 if err != nil { 196 return err 197 } 198 return apistatus.ErrFromStatus(res.Status()) 199 } 200 if f(buf[0]) { 201 return nil 202 } 203 } 204 } 205 206 // Close ends reading list of the matched objects and returns the result of the operation 207 // along with the final results. Must be called after using the ObjectListReader. 208 // 209 // Exactly one return value is non-nil. By default, server status is returned in res structure. 210 // Any client's internal or transport errors are returned as Go built-in error. 211 // If Client is tuned to resolve FrostFS API statuses, then FrostFS failures 212 // codes are returned as error. 213 // 214 // Return statuses: 215 // - global (see Client docs); 216 // - *apistatus.ContainerNotFound; 217 // - *apistatus.ObjectAccessDenied; 218 // - *apistatus.SessionTokenExpired. 219 func (x *ObjectListReader) Close() (*ResObjectSearch, error) { 220 defer x.cancelCtxStream() 221 222 if x.err != nil && !errors.Is(x.err, io.EOF) { 223 return &x.res, x.err 224 } 225 226 return &x.res, nil 227 } 228 229 func (x *PrmObjectSearch) buildRequest(c *Client) (*v2object.SearchRequest, error) { 230 if x.ContainerID == nil { 231 return nil, errorMissingContainer 232 } 233 234 if len(x.XHeaders)%2 != 0 { 235 return nil, errorInvalidXHeaders 236 } 237 238 meta := new(v2session.RequestMetaHeader) 239 writeXHeadersToMeta(x.XHeaders, meta) 240 241 if x.BearerToken != nil { 242 v2BearerToken := new(acl.BearerToken) 243 x.BearerToken.WriteToV2(v2BearerToken) 244 meta.SetBearerToken(v2BearerToken) 245 } 246 247 if x.Session != nil { 248 v2SessionToken := new(v2session.Token) 249 x.Session.WriteToV2(v2SessionToken) 250 meta.SetSessionToken(v2SessionToken) 251 } 252 253 if x.Local { 254 meta.SetTTL(1) 255 } 256 cnrV2 := new(v2refs.ContainerID) 257 x.ContainerID.WriteToV2(cnrV2) 258 259 body := new(v2object.SearchRequestBody) 260 body.SetVersion(1) 261 body.SetContainerID(cnrV2) 262 body.SetFilters(x.Filters.ToV2()) 263 264 req := new(v2object.SearchRequest) 265 req.SetBody(body) 266 c.prepareRequest(req, meta) 267 268 return req, nil 269 } 270 271 // ObjectSearchInit initiates object selection through a remote server using FrostFS API protocol. 272 // 273 // The call only opens the transmission channel, explicit fetching of matched objects 274 // is done using the ObjectListReader. Exactly one return value is non-nil. 275 // Resulting reader must be finally closed. 276 // 277 // Returns an error if parameters are set incorrectly (see PrmObjectSearch docs). 278 // Context is required and must not be nil. It is used for network communication. 279 func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*ObjectListReader, error) { 280 req, err := prm.buildRequest(c) 281 if err != nil { 282 return nil, err 283 } 284 285 key := prm.Key 286 if key == nil { 287 key = &c.prm.Key 288 } 289 290 err = signature.SignServiceMessage(key, req) 291 if err != nil { 292 return nil, fmt.Errorf("sign request: %w", err) 293 } 294 295 var r ObjectListReader 296 ctx, r.cancelCtxStream = context.WithCancel(ctx) 297 298 r.stream, err = rpcapi.SearchObjects(&c.c, req, client.WithContext(ctx)) 299 if err != nil { 300 return nil, fmt.Errorf("open stream: %w", err) 301 } 302 r.client = c 303 304 return &r, nil 305 }