github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/imagestorage/image.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package imagestorage
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	jujutxn "github.com/juju/txn"
    14  	"gopkg.in/juju/blobstore.v2"
    15  	"gopkg.in/mgo.v2"
    16  	"gopkg.in/mgo.v2/bson"
    17  	"gopkg.in/mgo.v2/txn"
    18  
    19  	"github.com/juju/juju/mongo"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.state.imagestorage")
    23  
    24  const (
    25  	// imagemetadataC is the collection used to store image metadata.
    26  	imagemetadataC = "imagemetadata"
    27  
    28  	// ImagesDB is the database used to store image blobs.
    29  	ImagesDB = "osimages"
    30  )
    31  
    32  type imageStorage struct {
    33  	modelUUID          string
    34  	metadataCollection *mgo.Collection
    35  	blobDb             *mgo.Database
    36  }
    37  
    38  var _ Storage = (*imageStorage)(nil)
    39  
    40  // NewStorage constructs a new Storage that stores image blobs
    41  // in an "osimages" database. Image metadata is also stored in this
    42  // database in the "imagemetadata" collection.
    43  func NewStorage(
    44  	session *mgo.Session,
    45  	modelUUID string,
    46  ) Storage {
    47  	blobDb := session.DB(ImagesDB)
    48  	metadataCollection := blobDb.C(imagemetadataC)
    49  	return &imageStorage{
    50  		modelUUID,
    51  		metadataCollection,
    52  		blobDb,
    53  	}
    54  }
    55  
    56  // Override for testing.
    57  var getManagedStorage = func(session *mgo.Session) blobstore.ManagedStorage {
    58  	rs := blobstore.NewGridFS(ImagesDB, ImagesDB, session)
    59  	db := session.DB(ImagesDB)
    60  	metadataDb := db.With(session)
    61  	return blobstore.NewManagedStorage(metadataDb, rs)
    62  }
    63  
    64  func (s *imageStorage) getManagedStorage(session *mgo.Session) blobstore.ManagedStorage {
    65  	return getManagedStorage(session)
    66  }
    67  
    68  func (s *imageStorage) txnRunner(session *mgo.Session) jujutxn.Runner {
    69  	db := s.metadataCollection.Database
    70  	runnerDb := db.With(session)
    71  	return txnRunner(runnerDb)
    72  }
    73  
    74  // Override for testing.
    75  var txnRunner = func(db *mgo.Database) jujutxn.Runner {
    76  	return jujutxn.NewRunner(jujutxn.RunnerParams{Database: db})
    77  }
    78  
    79  // AddImage is defined on the Storage interface.
    80  func (s *imageStorage) AddImage(r io.Reader, metadata *Metadata) (resultErr error) {
    81  	session := s.blobDb.Session.Copy()
    82  	defer session.Close()
    83  	managedStorage := s.getManagedStorage(session)
    84  	path := imagePath(metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256)
    85  	if err := managedStorage.PutForBucket(s.modelUUID, path, r, metadata.Size); err != nil {
    86  		return errors.Annotate(err, "cannot store image")
    87  	}
    88  	defer func() {
    89  		if resultErr == nil {
    90  			return
    91  		}
    92  		err := managedStorage.RemoveForBucket(s.modelUUID, path)
    93  		if err != nil {
    94  			logger.Errorf("failed to remove image blob: %v", err)
    95  		}
    96  	}()
    97  
    98  	newDoc := imageMetadataDoc{
    99  		Id:        docId(metadata),
   100  		ModelUUID: s.modelUUID,
   101  		Kind:      metadata.Kind,
   102  		Series:    metadata.Series,
   103  		Arch:      metadata.Arch,
   104  		Size:      metadata.Size,
   105  		SHA256:    metadata.SHA256,
   106  		SourceURL: metadata.SourceURL,
   107  		Path:      path,
   108  		// TODO(fwereade): 2016-03-17 lp:1558657
   109  		Created: time.Now(),
   110  	}
   111  
   112  	// Add or replace metadata. If replacing, record the
   113  	// existing path so we can remove the blob later.
   114  	var oldPath string
   115  	buildTxn := func(attempt int) ([]txn.Op, error) {
   116  		op := txn.Op{
   117  			C:  imagemetadataC,
   118  			Id: newDoc.Id,
   119  		}
   120  
   121  		// On the first attempt we assume we're adding a new image blob.
   122  		// Subsequent attempts to add image will fetch the existing
   123  		// doc, record the old path, and attempt to update the
   124  		// size, path and hash fields.
   125  		if attempt == 0 {
   126  			op.Assert = txn.DocMissing
   127  			op.Insert = &newDoc
   128  		} else {
   129  			oldDoc, err := s.imageMetadataDoc(metadata.ModelUUID, metadata.Kind, metadata.Series, metadata.Arch)
   130  			if err != nil {
   131  				return nil, err
   132  			}
   133  			oldPath = oldDoc.Path
   134  			op.Assert = bson.D{{"path", oldPath}}
   135  			if oldPath != path {
   136  				op.Update = bson.D{{
   137  					"$set", bson.D{
   138  						{"size", metadata.Size},
   139  						{"sha256", metadata.SHA256},
   140  						{"path", path},
   141  					},
   142  				}}
   143  			}
   144  		}
   145  		return []txn.Op{op}, nil
   146  	}
   147  	txnRunner := s.txnRunner(session)
   148  	err := txnRunner.Run(buildTxn)
   149  	if err != nil {
   150  		return errors.Annotate(err, "cannot store image metadata")
   151  	}
   152  
   153  	if oldPath != "" && oldPath != path {
   154  		// Attempt to remove the old path. Failure is non-fatal.
   155  		err := managedStorage.RemoveForBucket(s.modelUUID, oldPath)
   156  		if err != nil {
   157  			logger.Errorf("failed to remove old image blob: %v", err)
   158  		} else {
   159  			logger.Debugf("removed old image blob")
   160  		}
   161  	}
   162  	return nil
   163  }
   164  
   165  // ListImages is defined on the Storage interface.
   166  func (s *imageStorage) ListImages(filter ImageFilter) ([]*Metadata, error) {
   167  	metadataDocs, err := s.listImageMetadataDocs(s.modelUUID, filter.Kind, filter.Series, filter.Arch)
   168  	if err != nil {
   169  		return nil, errors.Annotate(err, "cannot list image metadata")
   170  	}
   171  	result := make([]*Metadata, len(metadataDocs))
   172  	for i, metadataDoc := range metadataDocs {
   173  		result[i] = &Metadata{
   174  			ModelUUID: s.modelUUID,
   175  			Kind:      metadataDoc.Kind,
   176  			Series:    metadataDoc.Series,
   177  			Arch:      metadataDoc.Arch,
   178  			Size:      metadataDoc.Size,
   179  			SHA256:    metadataDoc.SHA256,
   180  			Created:   metadataDoc.Created,
   181  			SourceURL: metadataDoc.SourceURL,
   182  		}
   183  	}
   184  	return result, nil
   185  }
   186  
   187  // DeleteImage is defined on the Storage interface.
   188  func (s *imageStorage) DeleteImage(metadata *Metadata) (resultErr error) {
   189  	session := s.blobDb.Session.Copy()
   190  	defer session.Close()
   191  	managedStorage := s.getManagedStorage(session)
   192  	path := imagePath(metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256)
   193  	if err := managedStorage.RemoveForBucket(s.modelUUID, path); err != nil {
   194  		return errors.Annotate(err, "cannot remove image blob")
   195  	}
   196  	// Remove the metadata.
   197  	buildTxn := func(attempt int) ([]txn.Op, error) {
   198  		op := txn.Op{
   199  			C:      imagemetadataC,
   200  			Id:     docId(metadata),
   201  			Remove: true,
   202  		}
   203  		return []txn.Op{op}, nil
   204  	}
   205  	txnRunner := s.txnRunner(session)
   206  	err := txnRunner.Run(buildTxn)
   207  	// Metadata already removed, we don't care.
   208  	if err == mgo.ErrNotFound {
   209  		return nil
   210  	}
   211  	return errors.Annotate(err, "cannot remove image metadata")
   212  }
   213  
   214  // imageCloser encapsulates an image reader and session
   215  // so that both are closed together.
   216  type imageCloser struct {
   217  	io.ReadCloser
   218  	session *mgo.Session
   219  }
   220  
   221  func (c *imageCloser) Close() error {
   222  	c.session.Close()
   223  	return c.ReadCloser.Close()
   224  }
   225  
   226  // Image is defined on the Storage interface.
   227  func (s *imageStorage) Image(kind, series, arch string) (*Metadata, io.ReadCloser, error) {
   228  	metadataDoc, err := s.imageMetadataDoc(s.modelUUID, kind, series, arch)
   229  	if err != nil {
   230  		return nil, nil, err
   231  	}
   232  	session := s.blobDb.Session.Copy()
   233  	managedStorage := s.getManagedStorage(session)
   234  	image, err := s.imageBlob(managedStorage, metadataDoc.Path)
   235  	if err != nil {
   236  		return nil, nil, err
   237  	}
   238  	metadata := &Metadata{
   239  		ModelUUID: s.modelUUID,
   240  		Kind:      metadataDoc.Kind,
   241  		Series:    metadataDoc.Series,
   242  		Arch:      metadataDoc.Arch,
   243  		Size:      metadataDoc.Size,
   244  		SHA256:    metadataDoc.SHA256,
   245  		SourceURL: metadataDoc.SourceURL,
   246  		Created:   metadataDoc.Created,
   247  	}
   248  	imageResult := &imageCloser{
   249  		image,
   250  		session,
   251  	}
   252  	return metadata, imageResult, nil
   253  }
   254  
   255  type imageMetadataDoc struct {
   256  	Id        string    `bson:"_id"`
   257  	ModelUUID string    `bson:"modelUUID"`
   258  	Kind      string    `bson:"kind"`
   259  	Series    string    `bson:"series"`
   260  	Arch      string    `bson:"arch"`
   261  	Size      int64     `bson:"size"`
   262  	SHA256    string    `bson:"sha256"`
   263  	Path      string    `bson:"path"`
   264  	Created   time.Time `bson:"created"`
   265  	SourceURL string    `bson:"sourceurl"`
   266  }
   267  
   268  func (s *imageStorage) imageMetadataDoc(modelUUID, kind, series, arch string) (imageMetadataDoc, error) {
   269  	var doc imageMetadataDoc
   270  	id := fmt.Sprintf("%s-%s-%s-%s", modelUUID, kind, series, arch)
   271  	coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC)
   272  	defer closer()
   273  	err := coll.FindId(id).One(&doc)
   274  	if err == mgo.ErrNotFound {
   275  		return doc, errors.NotFoundf("%v image metadata", id)
   276  	} else if err != nil {
   277  		return doc, err
   278  	}
   279  	return doc, nil
   280  }
   281  
   282  func (s *imageStorage) listImageMetadataDocs(modelUUID, kind, series, arch string) ([]imageMetadataDoc, error) {
   283  	coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC)
   284  	defer closer()
   285  	imageDocs := []imageMetadataDoc{}
   286  	filter := bson.D{{"modelUUID", modelUUID}}
   287  	if kind != "" {
   288  		filter = append(filter, bson.DocElem{"kind", kind})
   289  	}
   290  	if series != "" {
   291  		filter = append(filter, bson.DocElem{"series", series})
   292  	}
   293  	if arch != "" {
   294  		filter = append(filter, bson.DocElem{"arch", arch})
   295  	}
   296  	err := coll.Find(filter).All(&imageDocs)
   297  	return imageDocs, err
   298  }
   299  
   300  func (s *imageStorage) imageBlob(managedStorage blobstore.ManagedStorage, path string) (io.ReadCloser, error) {
   301  	r, _, err := managedStorage.GetForBucket(s.modelUUID, path)
   302  	return r, err
   303  }
   304  
   305  // imagePath returns the storage path for the specified image.
   306  func imagePath(kind, series, arch, checksum string) string {
   307  	return fmt.Sprintf("images/%s-%s-%s:%s", kind, series, arch, checksum)
   308  }
   309  
   310  // docId returns an id for the mongo image metadata document.
   311  func docId(metadata *Metadata) string {
   312  	return fmt.Sprintf("%s-%s-%s-%s", metadata.ModelUUID, metadata.Kind, metadata.Series, metadata.Arch)
   313  }