forked from TrueCloudLab/restic
azure/b2/gs/s3/swift: adapt cloud backend
This commit is contained in:
parent
e793c002ec
commit
d40f23e716
5 changed files with 116 additions and 3 deletions
|
@ -167,6 +167,20 @@ func (be *Backend) IsNotExist(err error) bool {
|
|||
return bloberror.HasCode(err, bloberror.BlobNotFound)
|
||||
}
|
||||
|
||||
func (be *Backend) IsPermanentError(err error) bool {
|
||||
if be.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
var aerr *azcore.ResponseError
|
||||
if errors.As(err, &aerr) {
|
||||
if aerr.StatusCode == http.StatusRequestedRangeNotSatisfiable || aerr.StatusCode == http.StatusUnauthorized || aerr.StatusCode == http.StatusForbidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Join combines path components with slashes.
|
||||
func (be *Backend) Join(p ...string) string {
|
||||
return path.Join(p...)
|
||||
|
@ -313,6 +327,11 @@ func (be *Backend) openReader(ctx context.Context, h backend.Handle, length int,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if length > 0 && (resp.ContentLength == nil || *resp.ContentLength != int64(length)) {
|
||||
_ = resp.Body.Close()
|
||||
return nil, &azcore.ResponseError{ErrorCode: "restic-file-too-short", StatusCode: http.StatusRequestedRangeNotSatisfiable}
|
||||
}
|
||||
|
||||
return resp.Body, err
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package b2
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -31,6 +32,8 @@ type b2Backend struct {
|
|||
canDelete bool
|
||||
}
|
||||
|
||||
var errTooShort = fmt.Errorf("file is too short")
|
||||
|
||||
// Billing happens in 1000 item granularity, but we are more interested in reducing the number of network round trips
|
||||
const defaultListMaxItems = 10 * 1000
|
||||
|
||||
|
@ -186,13 +189,36 @@ func (be *b2Backend) IsNotExist(err error) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (be *b2Backend) IsPermanentError(err error) bool {
|
||||
// the library unfortunately endlessly retries authentication errors
|
||||
return be.IsNotExist(err) || errors.Is(err, errTooShort)
|
||||
}
|
||||
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *b2Backend) Load(ctx context.Context, h backend.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
return util.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
return util.DefaultLoad(ctx, h, length, offset, be.openReader, func(rd io.Reader) error {
|
||||
if length == 0 {
|
||||
return fn(rd)
|
||||
}
|
||||
|
||||
// there is no direct way to efficiently check whether the file is too short
|
||||
// use a LimitedReader to track the number of bytes read
|
||||
limrd := &io.LimitedReader{R: rd, N: int64(length)}
|
||||
err := fn(limrd)
|
||||
|
||||
// check the underlying reader to be agnostic to however fn() handles the returned error
|
||||
_, rderr := rd.Read([]byte{0})
|
||||
if rderr == io.EOF && limrd.N != 0 {
|
||||
// file is too short
|
||||
return fmt.Errorf("%w: %v", errTooShort, err)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (be *b2Backend) openReader(ctx context.Context, h backend.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
|
|
|
@ -173,6 +173,21 @@ func (be *Backend) IsNotExist(err error) bool {
|
|||
return errors.Is(err, storage.ErrObjectNotExist)
|
||||
}
|
||||
|
||||
func (be *Backend) IsPermanentError(err error) bool {
|
||||
if be.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
var gerr *googleapi.Error
|
||||
if errors.As(err, &gerr) {
|
||||
if gerr.Code == http.StatusRequestedRangeNotSatisfiable || gerr.Code == http.StatusUnauthorized || gerr.Code == http.StatusForbidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Join combines path components with slashes.
|
||||
func (be *Backend) Join(p ...string) string {
|
||||
return path.Join(p...)
|
||||
|
@ -273,6 +288,11 @@ func (be *Backend) openReader(ctx context.Context, h backend.Handle, length int,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if length > 0 && r.Attrs.Size < offset+int64(length) {
|
||||
_ = r.Close()
|
||||
return nil, &googleapi.Error{Code: http.StatusRequestedRangeNotSatisfiable, Message: "restic-file-too-short"}
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
||||
|
||||
|
|
|
@ -229,6 +229,21 @@ func (be *Backend) IsNotExist(err error) bool {
|
|||
return errors.As(err, &e) && e.Code == "NoSuchKey"
|
||||
}
|
||||
|
||||
func (be *Backend) IsPermanentError(err error) bool {
|
||||
if be.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
var merr minio.ErrorResponse
|
||||
if errors.As(err, &merr) {
|
||||
if merr.Code == "InvalidRange" || merr.Code == "AccessDenied" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Join combines path components with slashes.
|
||||
func (be *Backend) Join(p ...string) string {
|
||||
return path.Join(p...)
|
||||
|
@ -384,11 +399,18 @@ func (be *Backend) openReader(ctx context.Context, h backend.Handle, length int,
|
|||
}
|
||||
|
||||
coreClient := minio.Core{Client: be.client}
|
||||
rd, _, _, err := coreClient.GetObject(ctx, be.cfg.Bucket, objName, opts)
|
||||
rd, info, _, err := coreClient.GetObject(ctx, be.cfg.Bucket, objName, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
if info.Size > 0 && info.Size != int64(length) {
|
||||
_ = rd.Close()
|
||||
return nil, minio.ErrorResponse{Code: "InvalidRange", Message: "restic-file-too-short"}
|
||||
}
|
||||
}
|
||||
|
||||
return rd, err
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,18 @@ func (be *beSwift) openReader(ctx context.Context, h backend.Handle, length int,
|
|||
|
||||
obj, _, err := be.conn.ObjectOpen(ctx, be.container, objName, false, headers)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "conn.ObjectOpen")
|
||||
return nil, fmt.Errorf("conn.ObjectOpen: %w", err)
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
// get response length, but don't cause backend calls
|
||||
cctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
objLength, e := obj.Length(cctx)
|
||||
if e == nil && objLength != int64(length) {
|
||||
_ = obj.Close()
|
||||
return nil, &swift.Error{StatusCode: http.StatusRequestedRangeNotSatisfiable, Text: "restic-file-too-short"}
|
||||
}
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
|
@ -242,6 +253,21 @@ func (be *beSwift) IsNotExist(err error) bool {
|
|||
return errors.As(err, &e) && e.StatusCode == http.StatusNotFound
|
||||
}
|
||||
|
||||
func (be *beSwift) IsPermanentError(err error) bool {
|
||||
if be.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
var serr *swift.Error
|
||||
if errors.As(err, &serr) {
|
||||
if serr.StatusCode == http.StatusRequestedRangeNotSatisfiable || serr.StatusCode == http.StatusUnauthorized || serr.StatusCode == http.StatusForbidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Delete removes all restic objects in the container.
|
||||
// It will not remove the container itself.
|
||||
func (be *beSwift) Delete(ctx context.Context) error {
|
||||
|
|
Loading…
Reference in a new issue