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  }