github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/blobstore"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	jujutxn "github.com/juju/txn"
    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  	envUUID            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  	envUUID string,
    46  ) Storage {
    47  	blobDb := session.DB(ImagesDB)
    48  	metadataCollection := blobDb.C(imagemetadataC)
    49  	return &imageStorage{
    50  		envUUID,
    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.PutForEnvironment(s.envUUID, 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.RemoveForEnvironment(s.envUUID, 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  		EnvUUID:   s.envUUID,
   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  		Created:   time.Now(),
   109  	}
   110  
   111  	// Add or replace metadata. If replacing, record the
   112  	// existing path so we can remove the blob later.
   113  	var oldPath string
   114  	buildTxn := func(attempt int) ([]txn.Op, error) {
   115  		op := txn.Op{
   116  			C:  imagemetadataC,
   117  			Id: newDoc.Id,
   118  		}
   119  
   120  		// On the first attempt we assume we're adding a new image blob.
   121  		// Subsequent attempts to add image will fetch the existing
   122  		// doc, record the old path, and attempt to update the
   123  		// size, path and hash fields.
   124  		if attempt == 0 {
   125  			op.Assert = txn.DocMissing
   126  			op.Insert = &newDoc
   127  		} else {
   128  			oldDoc, err := s.imageMetadataDoc(metadata.EnvUUID, metadata.Kind, metadata.Series, metadata.Arch)
   129  			if err != nil {
   130  				return nil, err
   131  			}
   132  			oldPath = oldDoc.Path
   133  			op.Assert = bson.D{{"path", oldPath}}
   134  			if oldPath != path {
   135  				op.Update = bson.D{{
   136  					"$set", bson.D{
   137  						{"size", metadata.Size},
   138  						{"sha256", metadata.SHA256},
   139  						{"path", path},
   140  					},
   141  				}}
   142  			}
   143  		}
   144  		return []txn.Op{op}, nil
   145  	}
   146  	txnRunner := s.txnRunner(session)
   147  	err := txnRunner.Run(buildTxn)
   148  	if err != nil {
   149  		return errors.Annotate(err, "cannot store image metadata")
   150  	}
   151  
   152  	if oldPath != "" && oldPath != path {
   153  		// Attempt to remove the old path. Failure is non-fatal.
   154  		err := managedStorage.RemoveForEnvironment(s.envUUID, oldPath)
   155  		if err != nil {
   156  			logger.Errorf("failed to remove old image blob: %v", err)
   157  		} else {
   158  			logger.Debugf("removed old image blob")
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  // ListImages is defined on the Storage interface.
   165  func (s *imageStorage) ListImages(filter ImageFilter) ([]*Metadata, error) {
   166  	metadataDocs, err := s.listImageMetadataDocs(s.envUUID, filter.Kind, filter.Series, filter.Arch)
   167  	if err != nil {
   168  		return nil, errors.Annotate(err, "cannot list image metadata")
   169  	}
   170  	result := make([]*Metadata, len(metadataDocs))
   171  	for i, metadataDoc := range metadataDocs {
   172  		result[i] = &Metadata{
   173  			EnvUUID:   s.envUUID,
   174  			Kind:      metadataDoc.Kind,
   175  			Series:    metadataDoc.Series,
   176  			Arch:      metadataDoc.Arch,
   177  			Size:      metadataDoc.Size,
   178  			SHA256:    metadataDoc.SHA256,
   179  			Created:   metadataDoc.Created,
   180  			SourceURL: metadataDoc.SourceURL,
   181  		}
   182  	}
   183  	return result, nil
   184  }
   185  
   186  // DeleteImage is defined on the Storage interface.
   187  func (s *imageStorage) DeleteImage(metadata *Metadata) (resultErr error) {
   188  	session := s.blobDb.Session.Copy()
   189  	defer session.Close()
   190  	managedStorage := s.getManagedStorage(session)
   191  	path := imagePath(metadata.Kind, metadata.Series, metadata.Arch, metadata.SHA256)
   192  	if err := managedStorage.RemoveForEnvironment(s.envUUID, path); err != nil {
   193  		return errors.Annotate(err, "cannot remove image blob")
   194  	}
   195  	// Remove the metadata.
   196  	buildTxn := func(attempt int) ([]txn.Op, error) {
   197  		op := txn.Op{
   198  			C:      imagemetadataC,
   199  			Id:     docId(metadata),
   200  			Remove: true,
   201  		}
   202  		return []txn.Op{op}, nil
   203  	}
   204  	txnRunner := s.txnRunner(session)
   205  	err := txnRunner.Run(buildTxn)
   206  	// Metadata already removed, we don't care.
   207  	if err == mgo.ErrNotFound {
   208  		return nil
   209  	}
   210  	return errors.Annotate(err, "cannot remove image metadata")
   211  }
   212  
   213  // imageCloser encapsulates an image reader and session
   214  // so that both are closed together.
   215  type imageCloser struct {
   216  	io.ReadCloser
   217  	session *mgo.Session
   218  }
   219  
   220  func (c *imageCloser) Close() error {
   221  	c.session.Close()
   222  	return c.ReadCloser.Close()
   223  }
   224  
   225  // Image is defined on the Storage interface.
   226  func (s *imageStorage) Image(kind, series, arch string) (*Metadata, io.ReadCloser, error) {
   227  	metadataDoc, err := s.imageMetadataDoc(s.envUUID, kind, series, arch)
   228  	if err != nil {
   229  		return nil, nil, err
   230  	}
   231  	session := s.blobDb.Session.Copy()
   232  	managedStorage := s.getManagedStorage(session)
   233  	image, err := s.imageBlob(managedStorage, metadataDoc.Path)
   234  	if err != nil {
   235  		return nil, nil, err
   236  	}
   237  	metadata := &Metadata{
   238  		EnvUUID:   s.envUUID,
   239  		Kind:      metadataDoc.Kind,
   240  		Series:    metadataDoc.Series,
   241  		Arch:      metadataDoc.Arch,
   242  		Size:      metadataDoc.Size,
   243  		SHA256:    metadataDoc.SHA256,
   244  		SourceURL: metadataDoc.SourceURL,
   245  		Created:   metadataDoc.Created,
   246  	}
   247  	imageResult := &imageCloser{
   248  		image,
   249  		session,
   250  	}
   251  	return metadata, imageResult, nil
   252  }
   253  
   254  type imageMetadataDoc struct {
   255  	Id        string    `bson:"_id"`
   256  	EnvUUID   string    `bson:"envuuid"`
   257  	Kind      string    `bson:"kind"`
   258  	Series    string    `bson:"series"`
   259  	Arch      string    `bson:"arch"`
   260  	Size      int64     `bson:"size"`
   261  	SHA256    string    `bson:"sha256"`
   262  	Path      string    `bson:"path"`
   263  	Created   time.Time `bson:"created"`
   264  	SourceURL string    `bson:"sourceurl"`
   265  }
   266  
   267  func (s *imageStorage) imageMetadataDoc(envUUID, kind, series, arch string) (imageMetadataDoc, error) {
   268  	var doc imageMetadataDoc
   269  	id := fmt.Sprintf("%s-%s-%s-%s", envUUID, kind, series, arch)
   270  	coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC)
   271  	defer closer()
   272  	err := coll.FindId(id).One(&doc)
   273  	if err == mgo.ErrNotFound {
   274  		return doc, errors.NotFoundf("%v image metadata", id)
   275  	} else if err != nil {
   276  		return doc, err
   277  	}
   278  	return doc, nil
   279  }
   280  
   281  func (s *imageStorage) listImageMetadataDocs(envUUID, kind, series, arch string) ([]imageMetadataDoc, error) {
   282  	coll, closer := mongo.CollectionFromName(s.metadataCollection.Database, imagemetadataC)
   283  	defer closer()
   284  	imageDocs := []imageMetadataDoc{}
   285  	filter := bson.D{{"envuuid", envUUID}}
   286  	if kind != "" {
   287  		filter = append(filter, bson.DocElem{"kind", kind})
   288  	}
   289  	if series != "" {
   290  		filter = append(filter, bson.DocElem{"series", series})
   291  	}
   292  	if arch != "" {
   293  		filter = append(filter, bson.DocElem{"arch", arch})
   294  	}
   295  	err := coll.Find(filter).All(&imageDocs)
   296  	return imageDocs, err
   297  }
   298  
   299  func (s *imageStorage) imageBlob(managedStorage blobstore.ManagedStorage, path string) (io.ReadCloser, error) {
   300  	r, _, err := managedStorage.GetForEnvironment(s.envUUID, path)
   301  	return r, err
   302  }
   303  
   304  // imagePath returns the storage path for the specified image.
   305  func imagePath(kind, series, arch, checksum string) string {
   306  	return fmt.Sprintf("images/%s-%s-%s:%s", kind, series, arch, checksum)
   307  }
   308  
   309  // docId returns an id for the mongo image metadata document.
   310  func docId(metadata *Metadata) string {
   311  	return fmt.Sprintf("%s-%s-%s-%s", metadata.EnvUUID, metadata.Kind, metadata.Series, metadata.Arch)
   312  }