Skip to content

Commit d9c441e

Browse files
authored
feat: Implement GetObjectRange for all storage providers (#13650)
1 parent 498f29a commit d9c441e

File tree

14 files changed

+229
-3
lines changed

14 files changed

+229
-3
lines changed

pkg/ingester-rf1/objstore/storage.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ func (m *Multi) GetObject(ctx context.Context, objectKey string) (io.ReadCloser,
9393
return s.GetObject(ctx, objectKey)
9494
}
9595

96+
func (m *Multi) GetObjectRange(ctx context.Context, objectKey string, off, length int64) (io.ReadCloser, error) {
97+
s, err := m.GetStoreFor(model.Now())
98+
if err != nil {
99+
return nil, err
100+
}
101+
return s.GetObjectRange(ctx, objectKey, off, length)
102+
}
103+
96104
func (m *Multi) List(ctx context.Context, prefix string, delimiter string) ([]client.StorageObject, []client.StorageCommonPrefix, error) {
97105
s, err := m.GetStoreFor(model.Now())
98106
if err != nil {

pkg/storage/chunk/client/alibaba/oss_object_client.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,26 @@ func (s *OssObjectClient) GetObject(ctx context.Context, objectKey string) (io.R
108108
return resp.Response.Body, int64(size), err
109109
}
110110

111+
// GetObject returns a reader and the size for the specified object key from the configured OSS bucket.
112+
func (s *OssObjectClient) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
113+
var resp *oss.GetObjectResult
114+
options := []oss.Option{
115+
oss.Range(offset, offset+length-1),
116+
}
117+
err := instrument.CollectedRequest(ctx, "OSS.GetObject", ossRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
118+
var requestErr error
119+
resp, requestErr = s.defaultBucket.DoGetObject(&oss.GetObjectRequest{ObjectKey: objectKey}, options)
120+
if requestErr != nil {
121+
return requestErr
122+
}
123+
return nil
124+
})
125+
if err != nil {
126+
return nil, err
127+
}
128+
return resp.Response.Body, err
129+
}
130+
111131
// PutObject puts the specified bytes into the configured OSS bucket at the provided key
112132
func (s *OssObjectClient) PutObject(ctx context.Context, objectKey string, object io.Reader) error {
113133
return instrument.CollectedRequest(ctx, "OSS.PutObject", ossRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {

pkg/storage/chunk/client/aws/s3_storage_client.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,40 @@ func (a *S3ObjectClient) GetObject(ctx context.Context, objectKey string) (io.Re
380380
return nil, 0, errors.Wrap(lastErr, "failed to get s3 object")
381381
}
382382

383+
// GetObject from the store
384+
func (a *S3ObjectClient) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
385+
var resp *s3.GetObjectOutput
386+
387+
// Map the key into a bucket
388+
bucket := a.bucketFromKey(objectKey)
389+
390+
var lastErr error
391+
392+
retries := backoff.New(ctx, a.cfg.BackoffConfig)
393+
for retries.Ongoing() {
394+
if ctx.Err() != nil {
395+
return nil, errors.Wrap(ctx.Err(), "ctx related error during s3 getObject")
396+
}
397+
398+
lastErr = loki_instrument.TimeRequest(ctx, "S3.GetObject", s3RequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
399+
var requestErr error
400+
resp, requestErr = a.hedgedS3.GetObjectWithContext(ctx, &s3.GetObjectInput{
401+
Bucket: aws.String(bucket),
402+
Key: aws.String(objectKey),
403+
Range: aws.String(fmt.Sprintf("bytes=%d-%d", offset, offset+length-1)),
404+
})
405+
return requestErr
406+
})
407+
408+
if lastErr == nil && resp.Body != nil {
409+
return resp.Body, nil
410+
}
411+
retries.Wait()
412+
}
413+
414+
return nil, errors.Wrap(lastErr, "failed to get s3 object")
415+
}
416+
383417
// PutObject into the store
384418
func (a *S3ObjectClient) PutObject(ctx context.Context, objectKey string, object io.Reader) error {
385419
return loki_instrument.TimeRequest(ctx, "S3.PutObject", s3RequestDuration, instrument.ErrorCode, func(ctx context.Context) error {

pkg/storage/chunk/client/azure/blob_storage_client.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (b *BlobStorage) GetObject(ctx context.Context, objectKey string) (io.ReadC
249249
)
250250
err := loki_instrument.TimeRequest(ctx, "azure.GetObject", instrument.NewHistogramCollector(b.metrics.requestDuration), instrument.ErrorCode, func(ctx context.Context) error {
251251
var err error
252-
rc, size, err = b.getObject(ctx, objectKey)
252+
rc, size, err = b.getObject(ctx, objectKey, 0, 0)
253253
return err
254254
})
255255
b.metrics.egressBytesTotal.Add(float64(size))
@@ -262,14 +262,43 @@ func (b *BlobStorage) GetObject(ctx context.Context, objectKey string) (io.ReadC
262262
return client_util.NewReadCloserWithContextCancelFunc(rc, cancel), size, nil
263263
}
264264

265-
func (b *BlobStorage) getObject(ctx context.Context, objectKey string) (rc io.ReadCloser, size int64, err error) {
265+
// GetObject returns a reader and the size for the specified object key.
266+
func (b *BlobStorage) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
267+
var cancel context.CancelFunc = func() {}
268+
if b.cfg.RequestTimeout > 0 {
269+
ctx, cancel = context.WithTimeout(ctx, (time.Duration(b.cfg.MaxRetries)*b.cfg.RequestTimeout)+(time.Duration(b.cfg.MaxRetries-1)*b.cfg.MaxRetryDelay)) // timeout only after azure client's built in retries
270+
}
271+
272+
var (
273+
size int64
274+
rc io.ReadCloser
275+
)
276+
err := loki_instrument.TimeRequest(ctx, "azure.GetObject", instrument.NewHistogramCollector(b.metrics.requestDuration), instrument.ErrorCode, func(ctx context.Context) error {
277+
var err error
278+
rc, size, err = b.getObject(ctx, objectKey, offset, length)
279+
return err
280+
})
281+
b.metrics.egressBytesTotal.Add(float64(size))
282+
if err != nil {
283+
// cancel the context if there is an error.
284+
cancel()
285+
return nil, err
286+
}
287+
// else return a wrapped ReadCloser which cancels the context while closing the reader.
288+
return client_util.NewReadCloserWithContextCancelFunc(rc, cancel), nil
289+
}
290+
291+
func (b *BlobStorage) getObject(ctx context.Context, objectKey string, offset, length int64) (rc io.ReadCloser, size int64, err error) {
292+
if offset == 0 && length == 0 {
293+
length = azblob.CountToEnd // azblob.CountToEnd == 0 but leaving this here for clarity
294+
}
266295
blockBlobURL, err := b.getBlobURL(objectKey, true)
267296
if err != nil {
268297
return nil, 0, err
269298
}
270299

271300
// Request access to the blob
272-
downloadResponse, err := blockBlobURL.Download(ctx, 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, noClientKey)
301+
downloadResponse, err := blockBlobURL.Download(ctx, offset, length, azblob.BlobAccessConditions{}, false, noClientKey)
273302
if err != nil {
274303
return nil, 0, err
275304
}

pkg/storage/chunk/client/baidubce/bos_storage_client.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ func (b *BOSObjectStorage) GetObject(ctx context.Context, objectKey string) (io.
117117
return res.Body, size, nil
118118
}
119119

120+
func (b *BOSObjectStorage) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
121+
var res *api.GetObjectResult
122+
err := instrument.CollectedRequest(ctx, "BOS.GetObject", bosRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
123+
var requestErr error
124+
res, requestErr = b.client.GetObject(b.cfg.BucketName, objectKey, nil, offset, offset+length-1)
125+
return requestErr
126+
})
127+
if err != nil {
128+
return nil, errors.Wrapf(err, "failed to get BOS object [ %s ]", objectKey)
129+
}
130+
return res.Body, nil
131+
}
132+
120133
func (b *BOSObjectStorage) List(ctx context.Context, prefix string, delimiter string) ([]client.StorageObject, []client.StorageCommonPrefix, error) {
121134
var storageObjects []client.StorageObject
122135
var commonPrefixes []client.StorageCommonPrefix

pkg/storage/chunk/client/congestion/controller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ func (a *AIMDController) GetObject(ctx context.Context, objectKey string) (io.Re
133133
return rc, sz, err
134134
}
135135

136+
func (a *AIMDController) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
137+
return a.inner.GetObjectRange(ctx, objectKey, offset, length)
138+
}
139+
136140
func (a *AIMDController) List(ctx context.Context, prefix string, delimiter string) ([]client.StorageObject, []client.StorageCommonPrefix, error) {
137141
return a.inner.List(ctx, prefix, delimiter)
138142
}
@@ -213,6 +217,9 @@ func (n *NoopController) PutObject(context.Context, string, io.Reader) error { r
213217
func (n *NoopController) GetObject(context.Context, string) (io.ReadCloser, int64, error) {
214218
return nil, 0, nil
215219
}
220+
func (n *NoopController) GetObjectRange(context.Context, string, int64, int64) (io.ReadCloser, error) {
221+
return nil, nil
222+
}
216223

217224
func (n *NoopController) List(context.Context, string, string) ([]client.StorageObject, []client.StorageCommonPrefix, error) {
218225
return nil, nil, nil

pkg/storage/chunk/client/congestion/controller_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ func (m *mockObjectClient) GetObject(context.Context, string) (io.ReadCloser, in
259259

260260
return io.NopCloser(strings.NewReader("bar")), 3, nil
261261
}
262+
func (m *mockObjectClient) GetObjectRange(context.Context, string, int64, int64) (io.ReadCloser, error) {
263+
panic("not implemented")
264+
}
262265

263266
func (m *mockObjectClient) ObjectExists(context.Context, string) (bool, error) {
264267
panic("not implemented")

pkg/storage/chunk/client/gcp/gcs_object_client.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,24 @@ func (s *GCSObjectClient) GetObject(ctx context.Context, objectKey string) (io.R
151151
return util.NewReadCloserWithContextCancelFunc(rc, cancel), size, nil
152152
}
153153

154+
// GetObject returns a reader and the size for the specified object key from the configured GCS bucket.
155+
func (s *GCSObjectClient) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
156+
var cancel context.CancelFunc = func() {}
157+
if s.cfg.RequestTimeout > 0 {
158+
ctx, cancel = context.WithTimeout(ctx, s.cfg.RequestTimeout)
159+
}
160+
161+
rangeReader, err := s.getsBuckets.Object(objectKey).NewRangeReader(ctx, offset, length)
162+
if err != nil {
163+
// cancel the context if there is an error.
164+
cancel()
165+
return nil, err
166+
}
167+
168+
// else return a wrapped ReadCloser which cancels the context while closing the reader.
169+
return util.NewReadCloserWithContextCancelFunc(rangeReader, cancel), nil
170+
}
171+
154172
func (s *GCSObjectClient) getObject(ctx context.Context, objectKey string) (rc io.ReadCloser, size int64, err error) {
155173
reader, err := s.getsBuckets.Object(objectKey).NewReader(ctx)
156174
if err != nil {

pkg/storage/chunk/client/ibmcloud/cos_object_client.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ibmcloud
33
import (
44
"context"
55
"flag"
6+
"fmt"
67
"hash/fnv"
78
"io"
89
"net"
@@ -368,6 +369,36 @@ func (c *COSObjectClient) GetObject(ctx context.Context, objectKey string) (io.R
368369
return nil, 0, errors.Wrap(err, "failed to get cos object")
369370
}
370371

372+
// GetObject returns a reader and the size for the specified object key from the configured S3 bucket.
373+
func (c *COSObjectClient) GetObjectRange(ctx context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
374+
var resp *cos.GetObjectOutput
375+
376+
// Map the key into a bucket
377+
bucket := c.bucketFromKey(objectKey)
378+
379+
retries := backoff.New(ctx, c.cfg.BackoffConfig)
380+
err := ctx.Err()
381+
for retries.Ongoing() {
382+
if ctx.Err() != nil {
383+
return nil, errors.Wrap(ctx.Err(), "ctx related error during cos getObject")
384+
}
385+
err = instrument.CollectedRequest(ctx, "COS.GetObject", cosRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
386+
var requestErr error
387+
resp, requestErr = c.hedgedCOS.GetObjectWithContext(ctx, &cos.GetObjectInput{
388+
Bucket: ibm.String(bucket),
389+
Key: ibm.String(objectKey),
390+
Range: ibm.String(fmt.Sprintf("bytes=%d-%d", offset, offset+length-1)),
391+
})
392+
return requestErr
393+
})
394+
if err == nil && resp.Body != nil {
395+
return resp.Body, nil
396+
}
397+
retries.Wait()
398+
}
399+
return nil, errors.Wrap(err, "failed to get cos object")
400+
}
401+
371402
// PutObject into the store
372403
func (c *COSObjectClient) PutObject(ctx context.Context, objectKey string, object io.Reader) error {
373404
return instrument.CollectedRequest(ctx, "COS.PutObject", cosRequestDuration, instrument.ErrorCode, func(ctx context.Context) error {

pkg/storage/chunk/client/local/fs_object_client.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ type FSObjectClient struct {
4646
pathSeparator string
4747
}
4848

49+
var _ client.ObjectClient = (*FSObjectClient)(nil)
50+
4951
// NewFSObjectClient makes a chunk.Client which stores chunks as files in the local filesystem.
5052
func NewFSObjectClient(cfg FSConfig) (*FSObjectClient, error) {
5153
// filepath.Clean cleans up the path by removing unwanted duplicate slashes, dots etc.
@@ -88,6 +90,28 @@ func (f *FSObjectClient) GetObject(_ context.Context, objectKey string) (io.Read
8890
return fl, stats.Size(), nil
8991
}
9092

93+
type SectionReadCloser struct {
94+
io.Reader
95+
closeFn func() error
96+
}
97+
98+
func (l SectionReadCloser) Close() error {
99+
return l.closeFn()
100+
}
101+
102+
// GetObject from the store
103+
func (f *FSObjectClient) GetObjectRange(_ context.Context, objectKey string, offset, length int64) (io.ReadCloser, error) {
104+
fl, err := os.Open(filepath.Join(f.cfg.Directory, filepath.FromSlash(objectKey)))
105+
if err != nil {
106+
return nil, err
107+
}
108+
closer := SectionReadCloser{
109+
Reader: io.NewSectionReader(fl, offset, length),
110+
closeFn: fl.Close,
111+
}
112+
return closer, nil
113+
}
114+
91115
// PutObject into the store
92116
func (f *FSObjectClient) PutObject(_ context.Context, objectKey string, object io.Reader) error {
93117
fullPath := filepath.Join(f.cfg.Directory, filepath.FromSlash(objectKey))

0 commit comments

Comments
 (0)