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 }