github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/driver/rados/rados.go (about) 1 // +build include_rados 2 3 package rados 4 5 import ( 6 "bytes" 7 "encoding/binary" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "path" 12 "strconv" 13 14 log "github.com/Sirupsen/logrus" 15 "github.com/docker/distribution/context" 16 storagedriver "github.com/docker/distribution/registry/storage/driver" 17 "github.com/docker/distribution/registry/storage/driver/base" 18 "github.com/docker/distribution/registry/storage/driver/factory" 19 "github.com/docker/distribution/uuid" 20 "github.com/noahdesu/go-ceph/rados" 21 ) 22 23 const driverName = "rados" 24 25 // Prefix all the stored blob 26 const objectBlobPrefix = "blob:" 27 28 // Stripes objects size to 4M 29 const defaultChunkSize = 4 << 20 30 const defaultXattrTotalSizeName = "total-size" 31 32 // Max number of keys fetched from omap at each read operation 33 const defaultKeysFetched = 1 34 35 //DriverParameters A struct that encapsulates all of the driver parameters after all values have been set 36 type DriverParameters struct { 37 poolname string 38 username string 39 chunksize uint64 40 } 41 42 func init() { 43 factory.Register(driverName, &radosDriverFactory{}) 44 } 45 46 // radosDriverFactory implements the factory.StorageDriverFactory interface 47 type radosDriverFactory struct{} 48 49 func (factory *radosDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) { 50 return FromParameters(parameters) 51 } 52 53 type driver struct { 54 Conn *rados.Conn 55 Ioctx *rados.IOContext 56 chunksize uint64 57 } 58 59 type baseEmbed struct { 60 base.Base 61 } 62 63 // Driver is a storagedriver.StorageDriver implementation backed by Ceph RADOS 64 // Objects are stored at absolute keys in the provided bucket. 65 type Driver struct { 66 baseEmbed 67 } 68 69 // FromParameters constructs a new Driver with a given parameters map 70 // Required parameters: 71 // - poolname: the ceph pool name 72 func FromParameters(parameters map[string]interface{}) (*Driver, error) { 73 74 pool, ok := parameters["poolname"] 75 if !ok { 76 return nil, fmt.Errorf("No poolname parameter provided") 77 } 78 79 username, ok := parameters["username"] 80 if !ok { 81 username = "" 82 } 83 84 chunksize := uint64(defaultChunkSize) 85 chunksizeParam, ok := parameters["chunksize"] 86 if ok { 87 chunksize, ok = chunksizeParam.(uint64) 88 if !ok { 89 return nil, fmt.Errorf("The chunksize parameter should be a number") 90 } 91 } 92 93 params := DriverParameters{ 94 fmt.Sprint(pool), 95 fmt.Sprint(username), 96 chunksize, 97 } 98 99 return New(params) 100 } 101 102 // New constructs a new Driver 103 func New(params DriverParameters) (*Driver, error) { 104 var conn *rados.Conn 105 var err error 106 107 if params.username != "" { 108 log.Infof("Opening connection to pool %s using user %s", params.poolname, params.username) 109 conn, err = rados.NewConnWithUser(params.username) 110 } else { 111 log.Infof("Opening connection to pool %s", params.poolname) 112 conn, err = rados.NewConn() 113 } 114 115 if err != nil { 116 return nil, err 117 } 118 119 err = conn.ReadDefaultConfigFile() 120 if err != nil { 121 return nil, err 122 } 123 124 err = conn.Connect() 125 if err != nil { 126 return nil, err 127 } 128 129 log.Infof("Connected") 130 131 ioctx, err := conn.OpenIOContext(params.poolname) 132 133 log.Infof("Connected to pool %s", params.poolname) 134 135 if err != nil { 136 return nil, err 137 } 138 139 d := &driver{ 140 Ioctx: ioctx, 141 Conn: conn, 142 chunksize: params.chunksize, 143 } 144 145 return &Driver{ 146 baseEmbed: baseEmbed{ 147 Base: base.Base{ 148 StorageDriver: d, 149 }, 150 }, 151 }, nil 152 } 153 154 // Implement the storagedriver.StorageDriver interface 155 156 func (d *driver) Name() string { 157 return driverName 158 } 159 160 // GetContent retrieves the content stored at "path" as a []byte. 161 func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { 162 rc, err := d.ReadStream(ctx, path, 0) 163 if err != nil { 164 return nil, err 165 } 166 defer rc.Close() 167 168 p, err := ioutil.ReadAll(rc) 169 if err != nil { 170 return nil, err 171 } 172 173 return p, nil 174 } 175 176 // PutContent stores the []byte content at a location designated by "path". 177 func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { 178 if _, err := d.WriteStream(ctx, path, 0, bytes.NewReader(contents)); err != nil { 179 return err 180 } 181 182 return nil 183 } 184 185 // ReadStream retrieves an io.ReadCloser for the content stored at "path" with a 186 // given byte offset. 187 type readStreamReader struct { 188 driver *driver 189 oid string 190 size uint64 191 offset uint64 192 } 193 194 func (r *readStreamReader) Read(b []byte) (n int, err error) { 195 // Determine the part available to read 196 bufferOffset := uint64(0) 197 bufferSize := uint64(len(b)) 198 199 // End of the object, read less than the buffer size 200 if bufferSize > r.size-r.offset { 201 bufferSize = r.size - r.offset 202 } 203 204 // Fill `b` 205 for bufferOffset < bufferSize { 206 // Get the offset in the object chunk 207 chunkedOid, chunkedOffset := r.driver.getChunkNameFromOffset(r.oid, r.offset) 208 209 // Determine the best size to read 210 bufferEndOffset := bufferSize 211 if bufferEndOffset-bufferOffset > r.driver.chunksize-chunkedOffset { 212 bufferEndOffset = bufferOffset + (r.driver.chunksize - chunkedOffset) 213 } 214 215 // Read the chunk 216 n, err = r.driver.Ioctx.Read(chunkedOid, b[bufferOffset:bufferEndOffset], chunkedOffset) 217 218 if err != nil { 219 return int(bufferOffset), err 220 } 221 222 bufferOffset += uint64(n) 223 r.offset += uint64(n) 224 } 225 226 // EOF if the offset is at the end of the object 227 if r.offset == r.size { 228 return int(bufferOffset), io.EOF 229 } 230 231 return int(bufferOffset), nil 232 } 233 234 func (r *readStreamReader) Close() error { 235 return nil 236 } 237 238 func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { 239 // get oid from filename 240 oid, err := d.getOid(path) 241 242 if err != nil { 243 return nil, err 244 } 245 246 // get object stat 247 stat, err := d.Stat(ctx, path) 248 249 if err != nil { 250 return nil, err 251 } 252 253 if offset > stat.Size() { 254 return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset} 255 } 256 257 return &readStreamReader{ 258 driver: d, 259 oid: oid, 260 size: uint64(stat.Size()), 261 offset: uint64(offset), 262 }, nil 263 } 264 265 func (d *driver) WriteStream(ctx context.Context, path string, offset int64, reader io.Reader) (totalRead int64, err error) { 266 buf := make([]byte, d.chunksize) 267 totalRead = 0 268 269 oid, err := d.getOid(path) 270 if err != nil { 271 switch err.(type) { 272 // Trying to write new object, generate new blob identifier for it 273 case storagedriver.PathNotFoundError: 274 oid = d.generateOid() 275 err = d.putOid(path, oid) 276 if err != nil { 277 return 0, err 278 } 279 default: 280 return 0, err 281 } 282 } else { 283 // Check total object size only for existing ones 284 totalSize, err := d.getXattrTotalSize(ctx, oid) 285 if err != nil { 286 return 0, err 287 } 288 289 // If offset if after the current object size, fill the gap with zeros 290 for totalSize < uint64(offset) { 291 sizeToWrite := d.chunksize 292 if totalSize-uint64(offset) < sizeToWrite { 293 sizeToWrite = totalSize - uint64(offset) 294 } 295 296 chunkName, chunkOffset := d.getChunkNameFromOffset(oid, uint64(totalSize)) 297 err = d.Ioctx.Write(chunkName, buf[:sizeToWrite], uint64(chunkOffset)) 298 if err != nil { 299 return totalRead, err 300 } 301 302 totalSize += sizeToWrite 303 } 304 } 305 306 // Writer 307 for { 308 // Align to chunk size 309 sizeRead := uint64(0) 310 sizeToRead := uint64(offset+totalRead) % d.chunksize 311 if sizeToRead == 0 { 312 sizeToRead = d.chunksize 313 } 314 315 // Read from `reader` 316 for sizeRead < sizeToRead { 317 nn, err := reader.Read(buf[sizeRead:sizeToRead]) 318 sizeRead += uint64(nn) 319 320 if err != nil { 321 if err != io.EOF { 322 return totalRead, err 323 } 324 325 break 326 } 327 } 328 329 // End of file and nothing was read 330 if sizeRead == 0 { 331 break 332 } 333 334 // Write chunk object 335 chunkName, chunkOffset := d.getChunkNameFromOffset(oid, uint64(offset+totalRead)) 336 err = d.Ioctx.Write(chunkName, buf[:sizeRead], uint64(chunkOffset)) 337 338 if err != nil { 339 return totalRead, err 340 } 341 342 // Update total object size as xattr in the first chunk of the object 343 err = d.setXattrTotalSize(oid, uint64(offset+totalRead)+sizeRead) 344 if err != nil { 345 return totalRead, err 346 } 347 348 totalRead += int64(sizeRead) 349 350 // End of file 351 if sizeRead < sizeToRead { 352 break 353 } 354 } 355 356 return totalRead, nil 357 } 358 359 // Stat retrieves the FileInfo for the given path, including the current size 360 func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { 361 // get oid from filename 362 oid, err := d.getOid(path) 363 364 if err != nil { 365 return nil, err 366 } 367 368 // the path is a virtual directory? 369 if oid == "" { 370 return storagedriver.FileInfoInternal{ 371 FileInfoFields: storagedriver.FileInfoFields{ 372 Path: path, 373 Size: 0, 374 IsDir: true, 375 }, 376 }, nil 377 } 378 379 // stat first chunk 380 stat, err := d.Ioctx.Stat(oid + "-0") 381 382 if err != nil { 383 return nil, err 384 } 385 386 // get total size of chunked object 387 totalSize, err := d.getXattrTotalSize(ctx, oid) 388 389 if err != nil { 390 return nil, err 391 } 392 393 return storagedriver.FileInfoInternal{ 394 FileInfoFields: storagedriver.FileInfoFields{ 395 Path: path, 396 Size: int64(totalSize), 397 ModTime: stat.ModTime, 398 }, 399 }, nil 400 } 401 402 // List returns a list of the objects that are direct descendants of the given path. 403 func (d *driver) List(ctx context.Context, dirPath string) ([]string, error) { 404 files, err := d.listDirectoryOid(dirPath) 405 406 if err != nil { 407 return nil, storagedriver.PathNotFoundError{Path: dirPath} 408 } 409 410 keys := make([]string, 0, len(files)) 411 for k := range files { 412 if k != dirPath { 413 keys = append(keys, path.Join(dirPath, k)) 414 } 415 } 416 417 return keys, nil 418 } 419 420 // Move moves an object stored at sourcePath to destPath, removing the original 421 // object. 422 func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { 423 // Get oid 424 oid, err := d.getOid(sourcePath) 425 426 if err != nil { 427 return err 428 } 429 430 // Move reference 431 err = d.putOid(destPath, oid) 432 433 if err != nil { 434 return err 435 } 436 437 // Delete old reference 438 err = d.deleteOid(sourcePath) 439 440 if err != nil { 441 return err 442 } 443 444 return nil 445 } 446 447 // Delete recursively deletes all objects stored at "path" and its subpaths. 448 func (d *driver) Delete(ctx context.Context, objectPath string) error { 449 // Get oid 450 oid, err := d.getOid(objectPath) 451 452 if err != nil { 453 return err 454 } 455 456 // Deleting virtual directory 457 if oid == "" { 458 objects, err := d.listDirectoryOid(objectPath) 459 if err != nil { 460 return err 461 } 462 463 for object := range objects { 464 err = d.Delete(ctx, path.Join(objectPath, object)) 465 if err != nil { 466 return err 467 } 468 } 469 } else { 470 // Delete object chunks 471 totalSize, err := d.getXattrTotalSize(ctx, oid) 472 473 if err != nil { 474 return err 475 } 476 477 for offset := uint64(0); offset < totalSize; offset += d.chunksize { 478 chunkName, _ := d.getChunkNameFromOffset(oid, offset) 479 480 err = d.Ioctx.Delete(chunkName) 481 if err != nil { 482 return err 483 } 484 } 485 486 // Delete reference 487 err = d.deleteOid(objectPath) 488 if err != nil { 489 return err 490 } 491 } 492 493 return nil 494 } 495 496 // URLFor returns a URL which may be used to retrieve the content stored at the given path. 497 // May return an UnsupportedMethodErr in certain StorageDriver implementations. 498 func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) { 499 return "", storagedriver.ErrUnsupportedMethod{} 500 } 501 502 // Generate a blob identifier 503 func (d *driver) generateOid() string { 504 return objectBlobPrefix + uuid.Generate().String() 505 } 506 507 // Reference a object and its hierarchy 508 func (d *driver) putOid(objectPath string, oid string) error { 509 directory := path.Dir(objectPath) 510 base := path.Base(objectPath) 511 createParentReference := true 512 513 // After creating this reference, skip the parents referencing since the 514 // hierarchy already exists 515 if oid == "" { 516 firstReference, err := d.Ioctx.GetOmapValues(directory, "", "", 1) 517 if (err == nil) && (len(firstReference) > 0) { 518 createParentReference = false 519 } 520 } 521 522 oids := map[string][]byte{ 523 base: []byte(oid), 524 } 525 526 // Reference object 527 err := d.Ioctx.SetOmap(directory, oids) 528 if err != nil { 529 return err 530 } 531 532 // Esure parent virtual directories 533 if createParentReference { 534 return d.putOid(directory, "") 535 } 536 537 return nil 538 } 539 540 // Get the object identifier from an object name 541 func (d *driver) getOid(objectPath string) (string, error) { 542 directory := path.Dir(objectPath) 543 base := path.Base(objectPath) 544 545 files, err := d.Ioctx.GetOmapValues(directory, "", base, 1) 546 547 if (err != nil) || (files[base] == nil) { 548 return "", storagedriver.PathNotFoundError{Path: objectPath} 549 } 550 551 return string(files[base]), nil 552 } 553 554 // List the objects of a virtual directory 555 func (d *driver) listDirectoryOid(path string) (list map[string][]byte, err error) { 556 return d.Ioctx.GetAllOmapValues(path, "", "", defaultKeysFetched) 557 } 558 559 // Remove a file from the files hierarchy 560 func (d *driver) deleteOid(objectPath string) error { 561 // Remove object reference 562 directory := path.Dir(objectPath) 563 base := path.Base(objectPath) 564 err := d.Ioctx.RmOmapKeys(directory, []string{base}) 565 566 if err != nil { 567 return err 568 } 569 570 // Remove virtual directory if empty (no more references) 571 firstReference, err := d.Ioctx.GetOmapValues(directory, "", "", 1) 572 573 if err != nil { 574 return err 575 } 576 577 if len(firstReference) == 0 { 578 // Delete omap 579 err := d.Ioctx.Delete(directory) 580 581 if err != nil { 582 return err 583 } 584 585 // Remove reference on parent omaps 586 if directory != "" { 587 return d.deleteOid(directory) 588 } 589 } 590 591 return nil 592 } 593 594 // Takes an offset in an chunked object and return the chunk name and a new 595 // offset in this chunk object 596 func (d *driver) getChunkNameFromOffset(oid string, offset uint64) (string, uint64) { 597 chunkID := offset / d.chunksize 598 chunkedOid := oid + "-" + strconv.FormatInt(int64(chunkID), 10) 599 chunkedOffset := offset % d.chunksize 600 return chunkedOid, chunkedOffset 601 } 602 603 // Set the total size of a chunked object `oid` 604 func (d *driver) setXattrTotalSize(oid string, size uint64) error { 605 // Convert uint64 `size` to []byte 606 xattr := make([]byte, binary.MaxVarintLen64) 607 binary.LittleEndian.PutUint64(xattr, size) 608 609 // Save the total size as a xattr in the first chunk 610 return d.Ioctx.SetXattr(oid+"-0", defaultXattrTotalSizeName, xattr) 611 } 612 613 // Get the total size of the chunked object `oid` stored as xattr 614 func (d *driver) getXattrTotalSize(ctx context.Context, oid string) (uint64, error) { 615 // Fetch xattr as []byte 616 xattr := make([]byte, binary.MaxVarintLen64) 617 xattrLength, err := d.Ioctx.GetXattr(oid+"-0", defaultXattrTotalSizeName, xattr) 618 619 if err != nil { 620 return 0, err 621 } 622 623 if xattrLength != len(xattr) { 624 context.GetLogger(ctx).Errorf("object %s xattr length mismatch: %d != %d", oid, xattrLength, len(xattr)) 625 return 0, storagedriver.PathNotFoundError{Path: oid} 626 } 627 628 // Convert []byte as uint64 629 totalSize := binary.LittleEndian.Uint64(xattr) 630 631 return totalSize, nil 632 }