github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/state/cloudimagemetadata/image.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloudimagemetadata
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	jujutxn "github.com/juju/txn"
    13  	"gopkg.in/mgo.v2"
    14  	"gopkg.in/mgo.v2/bson"
    15  	"gopkg.in/mgo.v2/txn"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.state.cloudimagemetadata")
    19  
    20  type storage struct {
    21  	envuuid    string
    22  	collection string
    23  	store      DataStore
    24  }
    25  
    26  var _ Storage = (*storage)(nil)
    27  
    28  // NewStorage constructs a new Storage that stores image metadata
    29  // in the provided data store.
    30  func NewStorage(envuuid, collectionName string, store DataStore) Storage {
    31  	return &storage{envuuid, collectionName, store}
    32  }
    33  
    34  var emptyMetadata = Metadata{}
    35  
    36  // SaveMetadata implements Storage.SaveMetadata and behaves as save-or-update.
    37  func (s *storage) SaveMetadata(metadata Metadata) error {
    38  	newDoc := s.mongoDoc(metadata)
    39  
    40  	buildTxn := func(attempt int) ([]txn.Op, error) {
    41  		op := txn.Op{
    42  			C:  s.collection,
    43  			Id: newDoc.Id,
    44  		}
    45  
    46  		// Check if this image metadata is already known.
    47  		existing, err := s.getMetadata(newDoc.Id)
    48  		if err != nil {
    49  			return nil, errors.Trace(err)
    50  		}
    51  		if existing.MetadataAttributes == metadata.MetadataAttributes {
    52  			// may need to updated imageId
    53  			if existing.ImageId != metadata.ImageId {
    54  				op.Assert = txn.DocExists
    55  				op.Update = bson.D{{"$set", bson.D{{"image_id", metadata.ImageId}}}}
    56  				logger.Debugf("updating cloud image id for metadata %v", newDoc.Id)
    57  			} else {
    58  				return nil, jujutxn.ErrNoOperations
    59  			}
    60  		} else {
    61  			op.Assert = txn.DocMissing
    62  			op.Insert = &newDoc
    63  			logger.Debugf("inserting cloud image metadata for %v", newDoc.Id)
    64  		}
    65  		return []txn.Op{op}, nil
    66  	}
    67  
    68  	err := s.store.RunTransaction(buildTxn)
    69  	if err != nil {
    70  		return errors.Annotatef(err, "cannot save metadata for cloud image %v", newDoc.ImageId)
    71  	}
    72  	return nil
    73  }
    74  
    75  func (s *storage) getMetadata(id string) (Metadata, error) {
    76  	coll, closer := s.store.GetCollection(s.collection)
    77  	defer closer()
    78  
    79  	var old imagesMetadataDoc
    80  	err := coll.Find(bson.D{{"_id", id}}).One(&old)
    81  	if err != nil {
    82  		if err == mgo.ErrNotFound {
    83  			return emptyMetadata, nil
    84  		}
    85  		return emptyMetadata, errors.Trace(err)
    86  	}
    87  	return old.metadata(), nil
    88  }
    89  
    90  // imagesMetadataDoc results in immutable records. Updates are effectively
    91  // a delate and an insert.
    92  type imagesMetadataDoc struct {
    93  	// EnvUUID is the environment identifier.
    94  	EnvUUID string `bson:"env-uuid"`
    95  
    96  	// Id contains unique key for cloud image metadata.
    97  	// This is an amalgamation of all deterministic metadata attributes to ensure
    98  	// that there can be a public and custom image for the same attributes set.
    99  	Id string `bson:"_id"`
   100  
   101  	// ImageId is an image identifier.
   102  	ImageId string `bson:"image_id"`
   103  
   104  	// Stream contains reference to a particular stream,
   105  	// for e.g. "daily" or "released"
   106  	Stream string `bson:"stream"`
   107  
   108  	// Region is the name of cloud region associated with the image.
   109  	Region string `bson:"region"`
   110  
   111  	// Series is Os version, for e.g. "quantal".
   112  	Series string `bson:"series"`
   113  
   114  	// Arch is the architecture for this cloud image, for e.g. "amd64"
   115  	Arch string `bson:"arch"`
   116  
   117  	// VirtType contains virtualisation type of the cloud image, for e.g. "pv", "hvm". "kvm".
   118  	VirtType string `bson:"virt_type,omitempty"`
   119  
   120  	// RootStorageType contains type of root storage, for e.g. "ebs", "instance".
   121  	RootStorageType string `bson:"root_storage_type,omitempty"`
   122  
   123  	// RootStorageSize contains size of root storage in gigabytes (GB).
   124  	RootStorageSize uint64 `bson:"root_storage_size"`
   125  
   126  	// DateCreated is the date/time when this doc was created.
   127  	DateCreated int64 `bson:"date_created"`
   128  
   129  	// Source describes where this image is coming from: is it public? custom?
   130  	Source SourceType `bson:"source"`
   131  }
   132  
   133  // SourceType values define source type.
   134  type SourceType string
   135  
   136  const (
   137  	// Public type identifies image as public.
   138  	Public SourceType = "public"
   139  
   140  	// Custom type identifies image as custom.
   141  	Custom SourceType = "custom"
   142  )
   143  
   144  func (m imagesMetadataDoc) metadata() Metadata {
   145  	r := Metadata{
   146  		MetadataAttributes{
   147  			Source:          m.Source,
   148  			Stream:          m.Stream,
   149  			Region:          m.Region,
   150  			Series:          m.Series,
   151  			Arch:            m.Arch,
   152  			RootStorageType: m.RootStorageType,
   153  			VirtType:        m.VirtType,
   154  		},
   155  		m.ImageId,
   156  	}
   157  	if m.RootStorageSize != 0 {
   158  		r.RootStorageSize = &m.RootStorageSize
   159  	}
   160  	return r
   161  }
   162  
   163  func (s *storage) mongoDoc(m Metadata) imagesMetadataDoc {
   164  	r := imagesMetadataDoc{
   165  		EnvUUID:         s.envuuid,
   166  		Id:              buildKey(m),
   167  		Stream:          m.Stream,
   168  		Region:          m.Region,
   169  		Series:          m.Series,
   170  		Arch:            m.Arch,
   171  		VirtType:        m.VirtType,
   172  		RootStorageType: m.RootStorageType,
   173  		ImageId:         m.ImageId,
   174  		DateCreated:     time.Now().UnixNano(),
   175  		Source:          m.Source,
   176  	}
   177  	if m.RootStorageSize != nil {
   178  		r.RootStorageSize = *m.RootStorageSize
   179  	}
   180  	return r
   181  }
   182  
   183  func buildKey(m Metadata) string {
   184  	return fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s",
   185  		m.Stream,
   186  		m.Region,
   187  		m.Series,
   188  		m.Arch,
   189  		m.VirtType,
   190  		m.RootStorageType,
   191  		m.Source)
   192  }
   193  
   194  // FindMetadata implements Storage.FindMetadata.
   195  // Results are sorted by date created and grouped by source.
   196  func (s *storage) FindMetadata(criteria MetadataFilter) (map[SourceType][]Metadata, error) {
   197  	coll, closer := s.store.GetCollection(s.collection)
   198  	defer closer()
   199  
   200  	searchCriteria := buildSearchClauses(criteria)
   201  	var docs []imagesMetadataDoc
   202  	if err := coll.Find(searchCriteria).Sort("date_created").All(&docs); err != nil {
   203  		return nil, errors.Trace(err)
   204  	}
   205  	if len(docs) == 0 {
   206  		return nil, errors.NotFoundf("matching cloud image metadata")
   207  	}
   208  
   209  	metadata := make(map[SourceType][]Metadata)
   210  	for _, doc := range docs {
   211  		one := doc.metadata()
   212  		metadata[one.Source] = append(metadata[one.Source], one)
   213  	}
   214  	return metadata, nil
   215  }
   216  
   217  func buildSearchClauses(criteria MetadataFilter) bson.D {
   218  	all := bson.D{}
   219  
   220  	if criteria.Stream != "" {
   221  		all = append(all, bson.DocElem{"stream", criteria.Stream})
   222  	}
   223  
   224  	if criteria.Region != "" {
   225  		all = append(all, bson.DocElem{"region", criteria.Region})
   226  	}
   227  
   228  	if len(criteria.Series) != 0 {
   229  		all = append(all, bson.DocElem{"series", bson.D{{"$in", criteria.Series}}})
   230  	}
   231  
   232  	if len(criteria.Arches) != 0 {
   233  		all = append(all, bson.DocElem{"arch", bson.D{{"$in", criteria.Arches}}})
   234  	}
   235  
   236  	if criteria.VirtType != "" {
   237  		all = append(all, bson.DocElem{"virt_type", criteria.VirtType})
   238  	}
   239  
   240  	if criteria.RootStorageType != "" {
   241  		all = append(all, bson.DocElem{"root_storage_type", criteria.RootStorageType})
   242  	}
   243  
   244  	if len(all.Map()) == 0 {
   245  		return nil
   246  	}
   247  	return all
   248  }
   249  
   250  // MetadataFilter contains all metadata attributes that alow to find a particular
   251  // cloud image metadata. Since size and source are not discriminating attributes
   252  // for cloud image metadata, they are not included in search criteria.
   253  type MetadataFilter struct {
   254  	// Region stores metadata region.
   255  	Region string `json:"region,omitempty"`
   256  
   257  	// Series stores all desired series.
   258  	Series []string `json:"series,omitempty"`
   259  
   260  	// Arches stores all desired architectures.
   261  	Arches []string `json:"arches,omitempty"`
   262  
   263  	// Stream can be "" or "released" for the default "released" stream,
   264  	// or "daily" for daily images, or any other stream that the available
   265  	// simplestreams metadata supports.
   266  	Stream string `json:"stream,omitempty"`
   267  
   268  	// VirtType stores virtualisation type.
   269  	VirtType string `json:"virt_type,omitempty"`
   270  
   271  	// RootStorageType stores storage type.
   272  	RootStorageType string `json:"root-storage-type,omitempty"`
   273  }