github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/backend/swift/swift.go (about)

     1  package swift
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"path"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/restic/restic/internal/backend"
    14  	"github.com/restic/restic/internal/debug"
    15  	"github.com/restic/restic/internal/errors"
    16  	"github.com/restic/restic/internal/restic"
    17  
    18  	"github.com/ncw/swift"
    19  )
    20  
    21  const connLimit = 10
    22  
    23  // beSwift is a backend which stores the data on a swift endpoint.
    24  type beSwift struct {
    25  	conn      *swift.Connection
    26  	sem       *backend.Semaphore
    27  	container string // Container name
    28  	prefix    string // Prefix of object names in the container
    29  	backend.Layout
    30  }
    31  
    32  // ensure statically that *beSwift implements restic.Backend.
    33  var _ restic.Backend = &beSwift{}
    34  
    35  // Open opens the swift backend at a container in region. The container is
    36  // created if it does not exist yet.
    37  func Open(cfg Config, rt http.RoundTripper) (restic.Backend, error) {
    38  	debug.Log("config %#v", cfg)
    39  
    40  	sem, err := backend.NewSemaphore(cfg.Connections)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	be := &beSwift{
    46  		conn: &swift.Connection{
    47  			UserName:       cfg.UserName,
    48  			Domain:         cfg.Domain,
    49  			ApiKey:         cfg.APIKey,
    50  			AuthUrl:        cfg.AuthURL,
    51  			Region:         cfg.Region,
    52  			Tenant:         cfg.Tenant,
    53  			TenantId:       cfg.TenantID,
    54  			TenantDomain:   cfg.TenantDomain,
    55  			TrustId:        cfg.TrustID,
    56  			StorageUrl:     cfg.StorageURL,
    57  			AuthToken:      cfg.AuthToken,
    58  			ConnectTimeout: time.Minute,
    59  			Timeout:        time.Minute,
    60  
    61  			Transport: rt,
    62  		},
    63  		sem:       sem,
    64  		container: cfg.Container,
    65  		prefix:    cfg.Prefix,
    66  		Layout: &backend.DefaultLayout{
    67  			Path: cfg.Prefix,
    68  			Join: path.Join,
    69  		},
    70  	}
    71  
    72  	// Authenticate if needed
    73  	if !be.conn.Authenticated() {
    74  		if err := be.conn.Authenticate(); err != nil {
    75  			return nil, errors.Wrap(err, "conn.Authenticate")
    76  		}
    77  	}
    78  
    79  	// Ensure container exists
    80  	switch _, _, err := be.conn.Container(be.container); err {
    81  	case nil:
    82  		// Container exists
    83  
    84  	case swift.ContainerNotFound:
    85  		err = be.createContainer(cfg.DefaultContainerPolicy)
    86  		if err != nil {
    87  			return nil, errors.Wrap(err, "beSwift.createContainer")
    88  		}
    89  
    90  	default:
    91  		return nil, errors.Wrap(err, "conn.Container")
    92  	}
    93  
    94  	return be, nil
    95  }
    96  
    97  func (be *beSwift) createContainer(policy string) error {
    98  	var h swift.Headers
    99  	if policy != "" {
   100  		h = swift.Headers{
   101  			"X-Storage-Policy": policy,
   102  		}
   103  	}
   104  
   105  	return be.conn.ContainerCreate(be.container, h)
   106  }
   107  
   108  // Location returns this backend's location (the container name).
   109  func (be *beSwift) Location() string {
   110  	return be.container
   111  }
   112  
   113  // Load returns a reader that yields the contents of the file at h at the
   114  // given offset. If length is nonzero, only a portion of the file is
   115  // returned. rd must be closed after use.
   116  func (be *beSwift) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
   117  	debug.Log("Load %v, length %v, offset %v", h, length, offset)
   118  	if err := h.Valid(); err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	if offset < 0 {
   123  		return nil, errors.New("offset is negative")
   124  	}
   125  
   126  	if length < 0 {
   127  		return nil, errors.Errorf("invalid length %d", length)
   128  	}
   129  
   130  	objName := be.Filename(h)
   131  
   132  	headers := swift.Headers{}
   133  	if offset > 0 {
   134  		headers["Range"] = fmt.Sprintf("bytes=%d-", offset)
   135  	}
   136  
   137  	if length > 0 {
   138  		headers["Range"] = fmt.Sprintf("bytes=%d-%d", offset, offset+int64(length)-1)
   139  	}
   140  
   141  	if _, ok := headers["Range"]; ok {
   142  		debug.Log("Load(%v) send range %v", h, headers["Range"])
   143  	}
   144  
   145  	be.sem.GetToken()
   146  	obj, _, err := be.conn.ObjectOpen(be.container, objName, false, headers)
   147  	if err != nil {
   148  		debug.Log("  err %v", err)
   149  		be.sem.ReleaseToken()
   150  		return nil, errors.Wrap(err, "conn.ObjectOpen")
   151  	}
   152  
   153  	return be.sem.ReleaseTokenOnClose(obj, nil), nil
   154  }
   155  
   156  // Save stores data in the backend at the handle.
   157  func (be *beSwift) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) {
   158  	if err = h.Valid(); err != nil {
   159  		return err
   160  	}
   161  
   162  	objName := be.Filename(h)
   163  
   164  	debug.Log("Save %v at %v", h, objName)
   165  
   166  	be.sem.GetToken()
   167  	defer be.sem.ReleaseToken()
   168  
   169  	// Check key does not already exist
   170  	switch _, _, err = be.conn.Object(be.container, objName); err {
   171  	case nil:
   172  		debug.Log("%v already exists", h)
   173  		return errors.New("key already exists")
   174  
   175  	case swift.ObjectNotFound:
   176  		// Ok, that's what we want
   177  
   178  	default:
   179  		return errors.Wrap(err, "conn.Object")
   180  	}
   181  
   182  	encoding := "binary/octet-stream"
   183  
   184  	debug.Log("PutObject(%v, %v, %v)", be.container, objName, encoding)
   185  	_, err = be.conn.ObjectPut(be.container, objName, rd, true, "", encoding, nil)
   186  	debug.Log("%v, err %#v", objName, err)
   187  
   188  	return errors.Wrap(err, "client.PutObject")
   189  }
   190  
   191  // Stat returns information about a blob.
   192  func (be *beSwift) Stat(ctx context.Context, h restic.Handle) (bi restic.FileInfo, err error) {
   193  	debug.Log("%v", h)
   194  
   195  	objName := be.Filename(h)
   196  
   197  	be.sem.GetToken()
   198  	defer be.sem.ReleaseToken()
   199  
   200  	obj, _, err := be.conn.Object(be.container, objName)
   201  	if err != nil {
   202  		debug.Log("Object() err %v", err)
   203  		return restic.FileInfo{}, errors.Wrap(err, "conn.Object")
   204  	}
   205  
   206  	return restic.FileInfo{Size: obj.Bytes}, nil
   207  }
   208  
   209  // Test returns true if a blob of the given type and name exists in the backend.
   210  func (be *beSwift) Test(ctx context.Context, h restic.Handle) (bool, error) {
   211  	objName := be.Filename(h)
   212  
   213  	be.sem.GetToken()
   214  	defer be.sem.ReleaseToken()
   215  
   216  	switch _, _, err := be.conn.Object(be.container, objName); err {
   217  	case nil:
   218  		return true, nil
   219  
   220  	case swift.ObjectNotFound:
   221  		return false, nil
   222  
   223  	default:
   224  		return false, errors.Wrap(err, "conn.Object")
   225  	}
   226  }
   227  
   228  // Remove removes the blob with the given name and type.
   229  func (be *beSwift) Remove(ctx context.Context, h restic.Handle) error {
   230  	objName := be.Filename(h)
   231  
   232  	be.sem.GetToken()
   233  	defer be.sem.ReleaseToken()
   234  
   235  	err := be.conn.ObjectDelete(be.container, objName)
   236  	debug.Log("Remove(%v) -> err %v", h, err)
   237  	return errors.Wrap(err, "conn.ObjectDelete")
   238  }
   239  
   240  // List returns a channel that yields all names of blobs of type t. A
   241  // goroutine is started for this. If the channel done is closed, sending
   242  // stops.
   243  func (be *beSwift) List(ctx context.Context, t restic.FileType) <-chan string {
   244  	debug.Log("listing %v", t)
   245  	ch := make(chan string)
   246  
   247  	prefix := be.Filename(restic.Handle{Type: t}) + "/"
   248  
   249  	go func() {
   250  		defer close(ch)
   251  
   252  		err := be.conn.ObjectsWalk(be.container, &swift.ObjectsOpts{Prefix: prefix},
   253  			func(opts *swift.ObjectsOpts) (interface{}, error) {
   254  				be.sem.GetToken()
   255  				newObjects, err := be.conn.ObjectNames(be.container, opts)
   256  				be.sem.ReleaseToken()
   257  
   258  				if err != nil {
   259  					return nil, errors.Wrap(err, "conn.ObjectNames")
   260  				}
   261  				for _, obj := range newObjects {
   262  					m := filepath.Base(strings.TrimPrefix(obj, prefix))
   263  					if m == "" {
   264  						continue
   265  					}
   266  
   267  					select {
   268  					case ch <- m:
   269  					case <-ctx.Done():
   270  						return nil, io.EOF
   271  					}
   272  				}
   273  				return newObjects, nil
   274  			})
   275  
   276  		if err != nil {
   277  			debug.Log("ObjectsWalk returned error: %v", err)
   278  		}
   279  	}()
   280  
   281  	return ch
   282  }
   283  
   284  // Remove keys for a specified backend type.
   285  func (be *beSwift) removeKeys(ctx context.Context, t restic.FileType) error {
   286  	for key := range be.List(ctx, t) {
   287  		err := be.Remove(ctx, restic.Handle{Type: t, Name: key})
   288  		if err != nil {
   289  			return err
   290  		}
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // IsNotExist returns true if the error is caused by a not existing file.
   297  func (be *beSwift) IsNotExist(err error) bool {
   298  	if e, ok := errors.Cause(err).(*swift.Error); ok {
   299  		return e.StatusCode == http.StatusNotFound
   300  	}
   301  
   302  	return false
   303  }
   304  
   305  // Delete removes all restic objects in the container.
   306  // It will not remove the container itself.
   307  func (be *beSwift) Delete(ctx context.Context) error {
   308  	alltypes := []restic.FileType{
   309  		restic.DataFile,
   310  		restic.KeyFile,
   311  		restic.LockFile,
   312  		restic.SnapshotFile,
   313  		restic.IndexFile}
   314  
   315  	for _, t := range alltypes {
   316  		err := be.removeKeys(ctx, t)
   317  		if err != nil {
   318  			return nil
   319  		}
   320  	}
   321  
   322  	err := be.Remove(ctx, restic.Handle{Type: restic.ConfigFile})
   323  	if err != nil && !be.IsNotExist(err) {
   324  		return err
   325  	}
   326  
   327  	return nil
   328  }
   329  
   330  // Close does nothing
   331  func (be *beSwift) Close() error { return nil }