github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/resources.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"path"
    11  	"strings"
    12  	"time"
    13  
    14  	charmresource "github.com/juju/charm/v12/resource"
    15  	"github.com/juju/clock"
    16  	"github.com/juju/collections/set"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/mgo/v3"
    19  	"github.com/juju/mgo/v3/bson"
    20  	"github.com/juju/mgo/v3/txn"
    21  	"github.com/juju/names/v5"
    22  	"github.com/juju/utils/v3"
    23  	"github.com/kr/pretty"
    24  
    25  	"github.com/juju/juju/core/resources"
    26  	"github.com/juju/juju/state/storage"
    27  )
    28  
    29  // Resources describes the state functionality for resources.
    30  type Resources interface {
    31  	// ListResources returns the list of resources for the given application.
    32  	ListResources(applicationID string) (resources.ApplicationResources, error)
    33  
    34  	// ListPendingResources returns the list of pending resources for
    35  	// the given application.
    36  	ListPendingResources(applicationID string) (resources.ApplicationResources, error)
    37  
    38  	// AddPendingResource adds the resource to the data store in a
    39  	// "pending" state. It will stay pending (and unavailable) until
    40  	// it is resolved. The returned ID is used to identify the pending
    41  	// resources when resolving it.
    42  	AddPendingResource(applicationID, userID string, chRes charmresource.Resource) (string, error)
    43  
    44  	// GetResource returns the identified resource.
    45  	GetResource(applicationID, name string) (resources.Resource, error)
    46  
    47  	// GetPendingResource returns the identified resource.
    48  	GetPendingResource(applicationID, name, pendingID string) (resources.Resource, error)
    49  
    50  	// SetResource adds the resource to blob storage and updates the metadata.
    51  	SetResource(applicationID, userID string, res charmresource.Resource, r io.Reader, _ IncrementCharmModifiedVersionType) (resources.Resource, error)
    52  
    53  	// SetUnitResource sets the resource metadata for a specific unit.
    54  	SetUnitResource(unitName, userID string, res charmresource.Resource) (resources.Resource, error)
    55  
    56  	// UpdatePendingResource adds the resource to blob storage and updates the metadata.
    57  	UpdatePendingResource(applicationID, pendingID, userID string, res charmresource.Resource, r io.Reader) (resources.Resource, error)
    58  
    59  	// OpenResource returns the metadata for a resource and a reader for the resource.
    60  	OpenResource(applicationID, name string) (resources.Resource, io.ReadCloser, error)
    61  
    62  	// OpenResourceForUniter returns the metadata for a resource and a reader for the resource.
    63  	OpenResourceForUniter(unitName, name string) (resources.Resource, io.ReadCloser, error)
    64  
    65  	// SetCharmStoreResources sets the "polled" resources for the
    66  	// application to the provided values.
    67  	SetCharmStoreResources(applicationID string, info []charmresource.Resource, lastPolled time.Time) error
    68  
    69  	// RemovePendingAppResources removes any pending application-level
    70  	// resources for the named application. This is used to clean up
    71  	// resources for a failed application deployment.
    72  	RemovePendingAppResources(applicationID string, pendingIDs map[string]string) error
    73  }
    74  
    75  // Resources returns the resources functionality for the current state.
    76  func (st *State) Resources() Resources {
    77  	return st.resources()
    78  }
    79  
    80  // Resources returns the resources functionality for the current state.
    81  func (st *State) resources() *resourcePersistence {
    82  	return &resourcePersistence{
    83  		st:                    st,
    84  		storage:               storage.NewStorage(st.ModelUUID(), st.MongoSession()),
    85  		dockerMetadataStorage: NewDockerMetadataStorage(st),
    86  	}
    87  }
    88  
    89  var rLogger = logger.Child("resource")
    90  
    91  const (
    92  	resourcesStagedIDSuffix     = "#staged"
    93  	resourcesCharmstoreIDSuffix = "#charmstore"
    94  )
    95  
    96  // A change in CharmModifiedVersion triggers the uniter to run the upgrade_charm
    97  // hook (and config hook). Increment required for a running unit to pick up
    98  // new resources from `attach` or when a charm is upgraded without a new charm
    99  // revision.
   100  //
   101  // IncrementCharmModifiedVersionType is the argument type for incrementing CharmModifiedVersion or not.
   102  type IncrementCharmModifiedVersionType bool
   103  
   104  const (
   105  	// IncrementCharmModifiedVersion means CharmModifiedVersion needs to be incremented.
   106  	IncrementCharmModifiedVersion IncrementCharmModifiedVersionType = true
   107  	// DoNotIncrementCharmModifiedVersion means CharmModifiedVersion should not be incremented.
   108  	DoNotIncrementCharmModifiedVersion IncrementCharmModifiedVersionType = false
   109  )
   110  
   111  // resourceID converts an external resource ID into an internal one.
   112  func resourceID(id, subType, subID string) string {
   113  	if subType == "" {
   114  		return fmt.Sprintf("resource#%s", id)
   115  	}
   116  	return fmt.Sprintf("resource#%s#%s-%s", id, subType, subID)
   117  }
   118  
   119  func applicationResourceID(id string) string {
   120  	return resourceID(id, "", "")
   121  }
   122  
   123  func pendingResourceID(id, pendingID string) string {
   124  	return resourceID(id, "pending", pendingID)
   125  }
   126  
   127  func charmStoreResourceID(id string) string {
   128  	return applicationResourceID(id) + resourcesCharmstoreIDSuffix
   129  }
   130  
   131  func unitResourceID(id, unitID string) string {
   132  	return resourceID(id, "unit", unitID)
   133  }
   134  
   135  // stagedResourceID converts an external resource ID into an internal
   136  // staged one.
   137  func stagedResourceID(id string) string {
   138  	return applicationResourceID(id) + resourcesStagedIDSuffix
   139  }
   140  
   141  // newPendingID generates a new unique identifier for a resource.
   142  func newPendingID() (string, error) {
   143  	uuid, err := utils.NewUUID()
   144  	if err != nil {
   145  		return "", errors.Annotate(err, "could not create new resource ID")
   146  	}
   147  	return uuid.String(), nil
   148  }
   149  
   150  // newAppResourceID produces a new ID to use for the resource in the model.
   151  func newAppResourceID(applicationID, name string) string {
   152  	return fmt.Sprintf("%s/%s", applicationID, name)
   153  }
   154  
   155  // resourceDoc is the top-level document for resources.
   156  type resourceDoc struct {
   157  	DocID     string `bson:"_id"`
   158  	ID        string `bson:"resource-id"`
   159  	PendingID string `bson:"pending-id"`
   160  
   161  	ApplicationID string `bson:"application-id"`
   162  	UnitID        string `bson:"unit-id"`
   163  
   164  	Name        string `bson:"name"`
   165  	Type        string `bson:"type"`
   166  	Path        string `bson:"path"`
   167  	Description string `bson:"description"`
   168  
   169  	Origin      string `bson:"origin"`
   170  	Revision    int    `bson:"revision"`
   171  	Fingerprint []byte `bson:"fingerprint"`
   172  	Size        int64  `bson:"size"`
   173  
   174  	Username  string    `bson:"username"`
   175  	Timestamp time.Time `bson:"timestamp-when-added"`
   176  
   177  	StoragePath string `bson:"storage-path"`
   178  
   179  	DownloadProgress *int64 `bson:"download-progress,omitempty"`
   180  
   181  	LastPolled time.Time `bson:"timestamp-when-last-polled"`
   182  }
   183  
   184  // storedResource holds all model-stored information for a resource.
   185  type storedResource struct {
   186  	resources.Resource
   187  
   188  	// storagePath is the path to where the resource content is stored.
   189  	storagePath string
   190  }
   191  
   192  // charmStoreResource holds the info for a resource as provided by the
   193  // charm store at as specific point in time.
   194  type charmStoreResource struct {
   195  	charmresource.Resource
   196  	id            string
   197  	applicationID string
   198  	lastPolled    time.Time
   199  }
   200  
   201  func charmStoreResource2Doc(id string, res charmStoreResource) *resourceDoc {
   202  	stored := storedResource{
   203  		Resource: resources.Resource{
   204  			Resource:      res.Resource,
   205  			ID:            res.id,
   206  			ApplicationID: res.applicationID,
   207  		},
   208  	}
   209  	doc := resource2doc(id, stored)
   210  	doc.LastPolled = res.lastPolled
   211  	return doc
   212  }
   213  
   214  // newCharmStoreResourceDoc generates a doc that represents the given resource.
   215  func newCharmStoreResourceDoc(res charmStoreResource) *resourceDoc {
   216  	fullID := charmStoreResourceID(res.id)
   217  	return charmStoreResource2Doc(fullID, res)
   218  }
   219  
   220  // newUnitResourceDoc generates a doc that represents the given resource.
   221  func newUnitResourceDoc(unitID string, stored storedResource) *resourceDoc {
   222  	fullID := unitResourceID(stored.ID, unitID)
   223  	return unitResource2Doc(fullID, unitID, stored)
   224  }
   225  
   226  // newResourceDoc generates a doc that represents the given resource.
   227  func newResourceDoc(stored storedResource) *resourceDoc {
   228  	fullID := applicationResourceID(stored.ID)
   229  	if stored.PendingID != "" {
   230  		fullID = pendingResourceID(stored.ID, stored.PendingID)
   231  	}
   232  	return resource2doc(fullID, stored)
   233  }
   234  
   235  // newStagedResourceDoc generates a staging doc that represents
   236  // the given resource.
   237  func newStagedResourceDoc(stored storedResource) *resourceDoc {
   238  	stagedID := stagedResourceID(stored.ID)
   239  	return resource2doc(stagedID, stored)
   240  }
   241  
   242  func unitResource2Doc(id, unitID string, stored storedResource) *resourceDoc {
   243  	doc := resource2doc(id, stored)
   244  	doc.UnitID = unitID
   245  	return doc
   246  }
   247  
   248  // resource2doc converts the resource into a DB doc.
   249  func resource2doc(id string, stored storedResource) *resourceDoc {
   250  	res := stored.Resource
   251  	// TODO(ericsnow) We may need to limit the resolution of timestamps
   252  	// in order to avoid some conversion problems from Mongo.
   253  	return &resourceDoc{
   254  		DocID:     id,
   255  		ID:        res.ID,
   256  		PendingID: res.PendingID,
   257  
   258  		ApplicationID: res.ApplicationID,
   259  
   260  		Name:        res.Name,
   261  		Type:        res.Type.String(),
   262  		Path:        res.Path,
   263  		Description: res.Description,
   264  
   265  		Origin:      res.Origin.String(),
   266  		Revision:    res.Revision,
   267  		Fingerprint: res.Fingerprint.Bytes(),
   268  		Size:        res.Size,
   269  
   270  		Username:  res.Username,
   271  		Timestamp: res.Timestamp,
   272  
   273  		StoragePath: stored.storagePath,
   274  	}
   275  }
   276  
   277  // doc2resource returns the resource info represented by the doc.
   278  func doc2resource(doc resourceDoc) (storedResource, error) {
   279  	res, err := doc2basicResource(doc)
   280  	if err != nil {
   281  		return storedResource{}, errors.Trace(err)
   282  	}
   283  
   284  	stored := storedResource{
   285  		Resource:    res,
   286  		storagePath: doc.StoragePath,
   287  	}
   288  	return stored, nil
   289  }
   290  
   291  // doc2basicResource returns the resource info represented by the doc.
   292  func doc2basicResource(doc resourceDoc) (resources.Resource, error) {
   293  	var res resources.Resource
   294  
   295  	resType, err := charmresource.ParseType(doc.Type)
   296  	if err != nil {
   297  		return res, errors.Annotate(err, "got invalid data from DB")
   298  	}
   299  
   300  	origin, err := charmresource.ParseOrigin(doc.Origin)
   301  	if err != nil {
   302  		return res, errors.Annotate(err, "got invalid data from DB")
   303  	}
   304  
   305  	fp, err := resources.DeserializeFingerprint(doc.Fingerprint)
   306  	if err != nil {
   307  		return res, errors.Annotate(err, "got invalid data from DB")
   308  	}
   309  
   310  	res = resources.Resource{
   311  		Resource: charmresource.Resource{
   312  			Meta: charmresource.Meta{
   313  				Name:        doc.Name,
   314  				Type:        resType,
   315  				Path:        doc.Path,
   316  				Description: doc.Description,
   317  			},
   318  			Origin:      origin,
   319  			Revision:    doc.Revision,
   320  			Fingerprint: fp,
   321  			Size:        doc.Size,
   322  		},
   323  		ID:            doc.ID,
   324  		PendingID:     doc.PendingID,
   325  		ApplicationID: doc.ApplicationID,
   326  		Username:      doc.Username,
   327  		Timestamp:     doc.Timestamp,
   328  	}
   329  	if err := res.Validate(); err != nil {
   330  		return res, errors.Annotate(err, "got invalid data from DB")
   331  	}
   332  	return res, nil
   333  }
   334  
   335  // storagePath returns the path used as the location where the resource
   336  // is stored in state storage. This requires that the returned string
   337  // be unique and that it be organized in a structured way. In this case
   338  // we start with a top-level (the application), then under that application use
   339  // the "resources" section. The provided ID is located under there.
   340  func storagePath(name, applicationID, pendingID string) string {
   341  	// TODO(ericsnow) Use applications/<application>/resources/<resource>?
   342  	id := name
   343  	if pendingID != "" {
   344  		// TODO(ericsnow) How to resolve this later?
   345  		id += "-" + pendingID
   346  	}
   347  	return path.Join("application-"+applicationID, "resources", id)
   348  }
   349  
   350  // resourcePersistence provides the persistence
   351  // functionality for resources.
   352  type resourcePersistence struct {
   353  	st                    *State
   354  	storage               storage.Storage
   355  	dockerMetadataStorage DockerMetadataStorage
   356  }
   357  
   358  // One gets the identified document from the collection.
   359  func (sp *resourcePersistence) one(collName, id string, doc interface{}) error {
   360  	coll, closeColl := sp.st.db().GetCollection(collName)
   361  	defer closeColl()
   362  
   363  	err := coll.FindId(id).One(doc)
   364  	if err == mgo.ErrNotFound {
   365  		return errors.NotFoundf(id)
   366  	}
   367  	if err != nil {
   368  		return errors.Trace(err)
   369  	}
   370  	return nil
   371  }
   372  
   373  // All gets all documents from the collection matching the query.
   374  func (p *resourcePersistence) all(collName string, query, docs interface{}) error {
   375  	coll, closeColl := p.st.db().GetCollection(collName)
   376  	defer closeColl()
   377  
   378  	if err := coll.Find(query).All(docs); err != nil {
   379  		return errors.Trace(err)
   380  	}
   381  	return nil
   382  }
   383  
   384  // resources returns the resource docs for the given application.
   385  func (p *resourcePersistence) resources(applicationID string) ([]resourceDoc, error) {
   386  	rLogger.Tracef("querying db for resources for %q", applicationID)
   387  	var docs []resourceDoc
   388  	query := bson.D{{"application-id", applicationID}}
   389  	if err := p.all(resourcesC, query, &docs); err != nil {
   390  		return nil, errors.Trace(err)
   391  	}
   392  	rLogger.Tracef("found %d resources", len(docs))
   393  	return docs, nil
   394  }
   395  
   396  func (p *resourcePersistence) unitResources(unitID string) ([]resourceDoc, error) {
   397  	var docs []resourceDoc
   398  	query := bson.D{{"unit-id", unitID}}
   399  	if err := p.all(resourcesC, query, &docs); err != nil {
   400  		return nil, errors.Trace(err)
   401  	}
   402  	return docs, nil
   403  }
   404  
   405  // getOne returns the resource that matches the provided model ID.
   406  func (p *resourcePersistence) getOne(resID string) (resourceDoc, error) {
   407  	id := applicationResourceID(resID)
   408  	rLogger.Tracef("querying db for resource %q as %q", resID, id)
   409  	var doc resourceDoc
   410  	if err := p.one(resourcesC, id, &doc); err != nil {
   411  		return doc, errors.Trace(err)
   412  	}
   413  	return doc, nil
   414  }
   415  
   416  // getOnePending returns the resource that matches the provided model ID.
   417  func (p *resourcePersistence) getOnePending(resID, pendingID string) (resourceDoc, error) {
   418  	id := pendingResourceID(resID, pendingID)
   419  	rLogger.Tracef("querying db for resource %q (pending %q) as %q", resID, pendingID, id)
   420  	var doc resourceDoc
   421  	if err := p.one(resourcesC, id, &doc); err != nil {
   422  		return doc, errors.Trace(err)
   423  	}
   424  	return doc, nil
   425  }
   426  
   427  func (p *resourcePersistence) verifyApplication(id string) error {
   428  	app, err := p.st.Application(id)
   429  	if err != nil {
   430  		return errors.Trace(err)
   431  	}
   432  	if app.Life() != Alive {
   433  		return errors.NewNotFound(nil, fmt.Sprintf("application %q dying or dead", id))
   434  	}
   435  	return nil
   436  }
   437  
   438  // listResources returns the info for each non-pending resource of the
   439  // identified application.
   440  func (p *resourcePersistence) listResources(applicationID string, pending bool) (resources.ApplicationResources, error) {
   441  	rLogger.Tracef("listing all resources for application %q, pending=%v", applicationID, pending)
   442  
   443  	docs, err := p.resources(applicationID)
   444  	if err != nil {
   445  		return resources.ApplicationResources{}, errors.Trace(err)
   446  	}
   447  
   448  	store := map[string]charmresource.Resource{}
   449  	units := map[names.UnitTag][]resources.Resource{}
   450  	downloadProgress := make(map[names.UnitTag]map[string]int64)
   451  
   452  	var results resources.ApplicationResources
   453  	for _, doc := range docs {
   454  		if !pending && doc.PendingID != "" {
   455  			continue
   456  		}
   457  		if pending && doc.PendingID == "" {
   458  			continue
   459  		}
   460  
   461  		res, err := doc2basicResource(doc)
   462  		if err != nil {
   463  			return resources.ApplicationResources{}, errors.Trace(err)
   464  		}
   465  		if strings.HasSuffix(doc.DocID, resourcesCharmstoreIDSuffix) {
   466  			store[res.Name] = res.Resource
   467  			continue
   468  		}
   469  		if doc.UnitID == "" {
   470  			results.Resources = append(results.Resources, res)
   471  			continue
   472  		}
   473  		tag := names.NewUnitTag(doc.UnitID)
   474  		units[tag] = append(units[tag], res)
   475  		if doc.DownloadProgress != nil {
   476  			if downloadProgress[tag] == nil {
   477  				downloadProgress[tag] = make(map[string]int64)
   478  			}
   479  			downloadProgress[tag][doc.Name] = *doc.DownloadProgress
   480  		}
   481  	}
   482  	for _, res := range results.Resources {
   483  		storeRes, ok := store[res.Name]
   484  		if ok {
   485  			results.CharmStoreResources = append(results.CharmStoreResources, storeRes)
   486  		}
   487  	}
   488  	for tag, res := range units {
   489  		results.UnitResources = append(results.UnitResources, resources.UnitResources{
   490  			Tag:              tag,
   491  			Resources:        res,
   492  			DownloadProgress: downloadProgress[tag],
   493  		})
   494  	}
   495  	if rLogger.IsTraceEnabled() {
   496  		rLogger.Tracef("found %d docs: %q", len(docs), pretty.Sprint(results))
   497  	}
   498  	return results, nil
   499  }
   500  
   501  // ListPendingResources returns the extended, model-related info for
   502  // each pending resource of the identifies application.
   503  func (p *resourcePersistence) ListPendingResources(applicationID string) (resources.ApplicationResources, error) {
   504  	rLogger.Tracef("listing all pending resources for application %q", applicationID)
   505  	res, err := p.listResources(applicationID, true)
   506  	if err != nil {
   507  		if err := p.verifyApplication(applicationID); err != nil {
   508  			return resources.ApplicationResources{}, errors.Trace(err)
   509  		}
   510  		return resources.ApplicationResources{}, errors.Trace(err)
   511  	}
   512  	return res, nil
   513  }
   514  
   515  // ListResources returns the non pending resource data for the given application ID.
   516  func (p *resourcePersistence) ListResources(applicationID string) (resources.ApplicationResources, error) {
   517  	res, err := p.listResources(applicationID, false)
   518  	if err != nil {
   519  		if err := p.verifyApplication(applicationID); err != nil {
   520  			return resources.ApplicationResources{}, errors.Trace(err)
   521  		}
   522  		return resources.ApplicationResources{}, errors.Trace(err)
   523  	}
   524  
   525  	units, err := allUnits(p.st, applicationID)
   526  	if err != nil {
   527  		return resources.ApplicationResources{}, errors.Trace(err)
   528  	}
   529  	for _, u := range units {
   530  		found := false
   531  		for _, unitRes := range res.UnitResources {
   532  			if u.Tag().String() == unitRes.Tag.String() {
   533  				found = true
   534  				break
   535  			}
   536  		}
   537  		if !found {
   538  			unitRes := resources.UnitResources{
   539  				Tag: u.UnitTag(),
   540  			}
   541  			res.UnitResources = append(res.UnitResources, unitRes)
   542  		}
   543  	}
   544  
   545  	return res, nil
   546  }
   547  
   548  // GetResource returns the resource data for the identified resource.
   549  func (p *resourcePersistence) GetResource(applicationID, name string) (resources.Resource, error) {
   550  	id := newAppResourceID(applicationID, name)
   551  	res, _, err := p.getResource(id)
   552  	if err != nil {
   553  		if err := p.verifyApplication(applicationID); err != nil {
   554  			return resources.Resource{}, errors.Trace(err)
   555  		}
   556  		return res, errors.Trace(err)
   557  	}
   558  	return res, nil
   559  }
   560  
   561  // GetPendingResource returns the resource data for the identified resource.
   562  func (p *resourcePersistence) GetPendingResource(applicationID, name, pendingID string) (resources.Resource, error) {
   563  	var res resources.Resource
   564  
   565  	resources, err := p.ListPendingResources(applicationID)
   566  	if err != nil {
   567  		// We do not call VerifyApplication() here because pending resources
   568  		// do not have to have an existing application.
   569  		return res, errors.Trace(err)
   570  	}
   571  
   572  	for _, res := range resources.Resources {
   573  		if res.Name == name && res.PendingID == pendingID {
   574  			return res, nil
   575  		}
   576  	}
   577  	return res, errors.NotFoundf("pending resource %q (%s)", name, pendingID)
   578  }
   579  
   580  // getResource returns the extended, model-related info for the non-pending
   581  // resource.
   582  func (p *resourcePersistence) getResource(id string) (res resources.Resource, storagePath string, _ error) {
   583  	rLogger.Tracef("get resource %q", id)
   584  	doc, err := p.getOne(id)
   585  	if err != nil {
   586  		return res, "", errors.Trace(err)
   587  	}
   588  
   589  	stored, err := doc2resource(doc)
   590  	if err != nil {
   591  		return res, "", errors.Trace(err)
   592  	}
   593  
   594  	return stored.Resource, stored.storagePath, nil
   595  }
   596  
   597  // SetResource stores the resource in the Juju model.
   598  func (p *resourcePersistence) SetResource(
   599  	applicationID, userID string,
   600  	chRes charmresource.Resource,
   601  	r io.Reader, incrementCharmModifiedVersion IncrementCharmModifiedVersionType,
   602  ) (resources.Resource, error) {
   603  	rLogger.Tracef("adding resource %q for application %q", chRes.Name, applicationID)
   604  	pendingID := ""
   605  	res, err := p.setResource(pendingID, applicationID, userID, chRes, r, incrementCharmModifiedVersion)
   606  	if err != nil {
   607  		return res, errors.Trace(err)
   608  	}
   609  	return res, nil
   610  }
   611  
   612  // SetUnitResource stores the resource info for a particular unit. The
   613  // resource must already be set for the application.
   614  func (p *resourcePersistence) SetUnitResource(unitName, userID string, chRes charmresource.Resource) (_ resources.Resource, outErr error) {
   615  	rLogger.Tracef("adding resource %q for unit %q", chRes.Name, unitName)
   616  	var empty resources.Resource
   617  
   618  	applicationID, err := names.UnitApplication(unitName)
   619  	if err != nil {
   620  		return empty, errors.Trace(err)
   621  	}
   622  
   623  	res := resources.Resource{
   624  		Resource:      chRes,
   625  		ID:            newAppResourceID(applicationID, chRes.Name),
   626  		ApplicationID: applicationID,
   627  	}
   628  	res.Username = userID
   629  	res.Timestamp = p.st.clock().Now().UTC()
   630  	if err := res.Validate(); err != nil {
   631  		return empty, errors.Annotate(err, "bad resource metadata")
   632  	}
   633  
   634  	if err := p.setUnitResourceProgress(unitName, res, nil); err != nil {
   635  		return empty, errors.Trace(err)
   636  	}
   637  
   638  	return res, nil
   639  }
   640  
   641  // SetCharmStoreResources sets the "polled" resources for the
   642  // application to the provided values.
   643  func (p *resourcePersistence) SetCharmStoreResources(applicationID string, info []charmresource.Resource, lastPolled time.Time) error {
   644  	for _, chRes := range info {
   645  		id := newAppResourceID(applicationID, chRes.Name)
   646  		if err := p.setCharmStoreResource(id, applicationID, chRes, lastPolled); err != nil {
   647  			return errors.Trace(err)
   648  		}
   649  		// TODO(ericsnow) Worry about extras? missing?
   650  	}
   651  
   652  	return nil
   653  }
   654  
   655  // setCharmStoreResource stores the resource info that was retrieved
   656  // from the charm store.
   657  func (p *resourcePersistence) setCharmStoreResource(id, applicationID string, res charmresource.Resource, lastPolled time.Time) error {
   658  	rLogger.Tracef("set charmstore %q resource %q", applicationID, res.Name)
   659  	if err := res.Validate(); err != nil {
   660  		return errors.Annotate(err, "bad resource")
   661  	}
   662  	if lastPolled.IsZero() {
   663  		return errors.NotValidf("empty last polled timestamp for charm resource %s/%s", applicationID, id)
   664  	}
   665  
   666  	csRes := charmStoreResource{
   667  		Resource:      res,
   668  		id:            id,
   669  		applicationID: applicationID,
   670  		lastPolled:    lastPolled,
   671  	}
   672  
   673  	buildTxn := func(attempt int) ([]txn.Op, error) {
   674  		// This is an "upsert".
   675  		var ops []txn.Op
   676  		switch attempt {
   677  		case 0:
   678  			ops = newInsertCharmStoreResourceOps(csRes)
   679  		case 1:
   680  			ops = newUpdateCharmStoreResourceOps(csRes)
   681  		default:
   682  			// Either insert or update will work so we should not get here.
   683  			return nil, errors.New("setting the resource failed")
   684  		}
   685  		// No pending resources so we always do this here.
   686  		ops = append(ops, applicationExistsOps(applicationID)...)
   687  		return ops, nil
   688  	}
   689  	if err := p.st.db().Run(buildTxn); err != nil {
   690  		return errors.Trace(err)
   691  	}
   692  	return nil
   693  }
   694  
   695  // AddPendingResource stores the resource in the Juju model.
   696  func (st resourcePersistence) AddPendingResource(applicationID, userID string, chRes charmresource.Resource) (pendingID string, err error) {
   697  	pendingID, err = newPendingID()
   698  	if err != nil {
   699  		return "", errors.Annotate(err, "could not generate resource ID")
   700  	}
   701  	rLogger.Debugf("adding pending resource %q for application %q (ID: %s)", chRes.Name, applicationID, pendingID)
   702  
   703  	if _, err := st.setResource(pendingID, applicationID, userID, chRes, nil, IncrementCharmModifiedVersion); err != nil {
   704  		return "", errors.Trace(err)
   705  	}
   706  
   707  	return pendingID, nil
   708  }
   709  
   710  // UpdatePendingResource stores the resource in the Juju model.
   711  func (st resourcePersistence) UpdatePendingResource(applicationID, pendingID, userID string, chRes charmresource.Resource, r io.Reader) (resources.Resource, error) {
   712  	rLogger.Tracef("updating pending resource %q (%s) for application %q", chRes.Name, pendingID, applicationID)
   713  	res, err := st.setResource(pendingID, applicationID, userID, chRes, r, IncrementCharmModifiedVersion)
   714  	if err != nil {
   715  		return res, errors.Trace(err)
   716  	}
   717  	return res, nil
   718  }
   719  
   720  // OpenResource returns metadata about the resource, and a reader for
   721  // the resource.
   722  func (p *resourcePersistence) OpenResource(applicationID, name string) (resources.Resource, io.ReadCloser, error) {
   723  	rLogger.Tracef("open resource %q of %q", name, applicationID)
   724  	id := newAppResourceID(applicationID, name)
   725  	resourceInfo, storagePath, err := p.getResource(id)
   726  	if err != nil {
   727  		if err := p.verifyApplication(applicationID); err != nil {
   728  			return resources.Resource{}, nil, errors.Trace(err)
   729  		}
   730  		return resources.Resource{}, nil, errors.Annotate(err, "while getting resource info")
   731  	}
   732  	if resourceInfo.IsPlaceholder() {
   733  		rLogger.Tracef("placeholder resource %q treated as not found", name)
   734  		return resources.Resource{}, nil, errors.NotFoundf("resource %q", name)
   735  	}
   736  
   737  	var resourceReader io.ReadCloser
   738  	var resSize int64
   739  	switch resourceInfo.Type {
   740  	case charmresource.TypeContainerImage:
   741  		resourceReader, resSize, err = p.dockerMetadataStorage.Get(resourceInfo.ID)
   742  	case charmresource.TypeFile:
   743  		resourceReader, resSize, err = p.storage.Get(storagePath)
   744  	default:
   745  		return resources.Resource{}, nil, errors.New("unknown resource type")
   746  	}
   747  	if err != nil {
   748  		return resources.Resource{}, nil, errors.Annotate(err, "while retrieving resource data")
   749  	}
   750  	switch resourceInfo.Type {
   751  	case charmresource.TypeContainerImage:
   752  		// Resource size only found at this stage in time as it's a response from the charmstore, not a stored file.
   753  		// Store it as it's used later for verification (in a separate call than this one)
   754  		resourceInfo.Size = resSize
   755  		if err := p.storeResourceInfo(resourceInfo); err != nil {
   756  			return resources.Resource{}, nil, errors.Annotate(err, "failed to update resource details with docker detail size")
   757  		}
   758  	case charmresource.TypeFile:
   759  		if resSize != resourceInfo.Size {
   760  			msg := "storage returned a size (%d) which doesn't match resource metadata (%d)"
   761  			return resources.Resource{}, nil, errors.Errorf(msg, resSize, resourceInfo.Size)
   762  		}
   763  	}
   764  
   765  	return resourceInfo, resourceReader, nil
   766  }
   767  
   768  // OpenResourceForUniter returns metadata about the resource and
   769  // a reader for the resource. The resource is associated with
   770  // the unit once the reader is completely exhausted.
   771  func (p *resourcePersistence) OpenResourceForUniter(unitName, resName string) (resources.Resource, io.ReadCloser, error) {
   772  	rLogger.Tracef("open resource %q for uniter %q", resName, unitName)
   773  
   774  	pendingID, err := newPendingID()
   775  	if err != nil {
   776  		return resources.Resource{}, nil, errors.Trace(err)
   777  	}
   778  
   779  	appName, err := names.UnitApplication(unitName)
   780  	if err != nil {
   781  		return resources.Resource{}, nil, errors.Trace(err)
   782  	}
   783  	resourceInfo, resourceReader, err := p.OpenResource(appName, resName)
   784  	if err != nil {
   785  		return resources.Resource{}, nil, errors.Trace(err)
   786  	}
   787  
   788  	pending := resourceInfo // a copy
   789  	pending.PendingID = pendingID
   790  
   791  	progress := int64(0)
   792  	if err := p.setUnitResourceProgress(unitName, pending, &progress); err != nil {
   793  		_ = resourceReader.Close()
   794  		return resources.Resource{}, nil, errors.Trace(err)
   795  	}
   796  
   797  	resourceReader = &unitSetter{
   798  		ReadCloser: resourceReader,
   799  		persist:    p,
   800  		unitName:   unitName,
   801  		pending:    pending,
   802  		resource:   resourceInfo,
   803  		clock:      clock.WallClock,
   804  	}
   805  
   806  	return resourceInfo, resourceReader, nil
   807  }
   808  
   809  // unitSetter records the resource as in use by a unit when the wrapped
   810  // reader has been fully read.
   811  type unitSetter struct {
   812  	io.ReadCloser
   813  	persist            *resourcePersistence
   814  	unitName           string
   815  	pending            resources.Resource
   816  	resource           resources.Resource
   817  	progress           int64
   818  	lastProgressUpdate time.Time
   819  	clock              clock.Clock
   820  }
   821  
   822  // Read implements io.Reader.
   823  func (u *unitSetter) Read(p []byte) (n int, err error) {
   824  	n, err = u.ReadCloser.Read(p)
   825  	if err == io.EOF {
   826  		// record that the unit is now using this version of the resource
   827  		if err := u.persist.setUnitResourceProgress(u.unitName, u.resource, nil); err != nil {
   828  			msg := "Failed to record that unit %q is using resource %q revision %v"
   829  			rLogger.Errorf(msg, u.unitName, u.resource.Name, u.resource.RevisionString())
   830  		}
   831  	} else {
   832  		u.progress += int64(n)
   833  		if time.Since(u.lastProgressUpdate) > time.Second {
   834  			u.lastProgressUpdate = u.clock.Now()
   835  			if err := u.persist.setUnitResourceProgress(u.unitName, u.pending, &u.progress); err != nil {
   836  				rLogger.Errorf("failed to track progress: %v", err)
   837  			}
   838  		}
   839  	}
   840  	return n, err
   841  }
   842  
   843  // stageResource adds the resource in a separate staging area
   844  // if the resource isn't already staged. If it is then
   845  // errors.AlreadyExists is returned. A wrapper around the staged
   846  // resource is returned which supports both finalizing and removing
   847  // the staged resource.
   848  func (p *resourcePersistence) stageResource(res resources.Resource, storagePath string) (*StagedResource, error) {
   849  	rLogger.Tracef("stage resource %q for %q", res.Name, res.ApplicationID)
   850  	if storagePath == "" {
   851  		return nil, errors.Errorf("missing storage path")
   852  	}
   853  
   854  	if err := res.Validate(); err != nil {
   855  		return nil, errors.Annotate(err, "bad resource")
   856  	}
   857  
   858  	stored := storedResource{
   859  		Resource:    res,
   860  		storagePath: storagePath,
   861  	}
   862  	staged := &StagedResource{
   863  		p:      p,
   864  		id:     res.ID,
   865  		stored: stored,
   866  	}
   867  	if err := staged.stage(); err != nil {
   868  		return nil, errors.Trace(err)
   869  	}
   870  	return staged, nil
   871  }
   872  
   873  func (p *resourcePersistence) setResource(
   874  	pendingID, applicationID, userID string,
   875  	chRes charmresource.Resource, r io.Reader,
   876  	incrementCharmModifiedVersion IncrementCharmModifiedVersionType,
   877  ) (resources.Resource, error) {
   878  	id := newAppResourceID(applicationID, chRes.Name)
   879  
   880  	res := resources.Resource{
   881  		Resource:      chRes,
   882  		ID:            id,
   883  		PendingID:     pendingID,
   884  		ApplicationID: applicationID,
   885  	}
   886  	if r != nil {
   887  		// TODO(ericsnow) Validate the user ID (or use a tag).
   888  		res.Username = userID
   889  		res.Timestamp = p.st.clock().Now().UTC()
   890  	}
   891  
   892  	if err := res.Validate(); err != nil {
   893  		return res, errors.Annotate(err, "bad resource metadata")
   894  	}
   895  
   896  	if r == nil {
   897  		if err := p.storeResourceInfo(res); err != nil {
   898  			return res, errors.Trace(err)
   899  		}
   900  	} else {
   901  		if err := p.storeResource(res, r, incrementCharmModifiedVersion); err != nil {
   902  			return res, errors.Trace(err)
   903  		}
   904  	}
   905  
   906  	return res, nil
   907  }
   908  
   909  func (p *resourcePersistence) getStored(res resources.Resource) (storedResource, error) {
   910  	doc, err := p.getOne(res.ID)
   911  	if errors.IsNotFound(err) {
   912  		err = errors.NotFoundf("resource %q", res.Name)
   913  	}
   914  	if err != nil {
   915  		return storedResource{}, errors.Trace(err)
   916  	}
   917  
   918  	stored, err := doc2resource(doc)
   919  	if err != nil {
   920  		return stored, errors.Trace(err)
   921  	}
   922  
   923  	return stored, nil
   924  }
   925  
   926  // storeResource stores the info for the resource.
   927  func (p *resourcePersistence) storeResourceInfo(res resources.Resource) error {
   928  	rLogger.Tracef("set resource %q for %q", res.Name, res.ApplicationID)
   929  	stored, err := p.getStored(res)
   930  	if errors.IsNotFound(err) {
   931  		stored = storedResource{Resource: res}
   932  	} else if err != nil {
   933  		return errors.Trace(err)
   934  	}
   935  	// TODO(ericsnow) Ensure that stored.Resource matches res? If we do
   936  	// so then the following line is unnecessary.
   937  	stored.Resource = res
   938  
   939  	if err := res.Validate(); err != nil {
   940  		return errors.Annotate(err, "bad resource")
   941  	}
   942  
   943  	buildTxn := func(attempt int) ([]txn.Op, error) {
   944  		// This is an "upsert".
   945  		var ops []txn.Op
   946  		switch attempt {
   947  		case 0:
   948  			ops = newInsertResourceOps(stored)
   949  		case 1:
   950  			ops = newUpdateResourceOps(stored)
   951  		default:
   952  			// Either insert or update will work so we should not get here.
   953  			return nil, errors.New("setting the resource failed")
   954  		}
   955  		if stored.PendingID == "" {
   956  			// Only non-pending resources must have an existing application.
   957  			ops = append(ops, applicationExistsOps(res.ApplicationID)...)
   958  		}
   959  		return ops, nil
   960  	}
   961  	if err := p.st.db().Run(buildTxn); err != nil {
   962  		return errors.Trace(err)
   963  	}
   964  	return nil
   965  }
   966  
   967  func (p *resourcePersistence) storeResource(res resources.Resource, r io.Reader, incrementCharmModifiedVersion IncrementCharmModifiedVersionType) (err error) {
   968  	// We use a staging approach for adding the resource metadata
   969  	// to the model. This is necessary because the resource data
   970  	// is stored separately and adding to both should be an atomic
   971  	// operation.
   972  
   973  	storagePath := storagePath(res.Name, res.ApplicationID, res.PendingID)
   974  	staged, err := p.stageResource(res, storagePath)
   975  	if err != nil {
   976  		return errors.Trace(err)
   977  	}
   978  	defer func() {
   979  		if err != nil {
   980  			if e := staged.Unstage(); e != nil {
   981  				rLogger.Errorf("could not unstage resource %q (application %q): %v", res.Name, res.ApplicationID, e)
   982  			}
   983  		}
   984  	}()
   985  
   986  	hash := res.Fingerprint.String()
   987  	switch res.Type {
   988  	case charmresource.TypeFile:
   989  		if err = p.storage.PutAndCheckHash(storagePath, r, res.Size, hash); err != nil {
   990  			return errors.Trace(err)
   991  		}
   992  	case charmresource.TypeContainerImage:
   993  		respBuf := new(bytes.Buffer)
   994  		_, err = respBuf.ReadFrom(r)
   995  		if err != nil {
   996  			return errors.Trace(err)
   997  		}
   998  		dockerDetails, err := resources.UnmarshalDockerResource(respBuf.Bytes())
   999  		if err != nil {
  1000  			return errors.Trace(err)
  1001  		}
  1002  		err = p.dockerMetadataStorage.Save(res.ID, dockerDetails)
  1003  		if err != nil {
  1004  			return errors.Trace(err)
  1005  		}
  1006  	}
  1007  
  1008  	if err = staged.Activate(incrementCharmModifiedVersion); err != nil {
  1009  		if e := p.storage.Remove(storagePath); e != nil {
  1010  			rLogger.Errorf("could not remove resource %q (application %q) from storage: %v", res.Name, res.ApplicationID, e)
  1011  		}
  1012  		return errors.Trace(err)
  1013  	}
  1014  	return nil
  1015  }
  1016  
  1017  // setUnitResourceProgress stores the resource info for a particular unit. The
  1018  // resource must already be set for the application. The provided progress
  1019  // is stored in the DB.
  1020  func (p *resourcePersistence) setUnitResourceProgress(unitID string, res resources.Resource, progress *int64) error {
  1021  	rLogger.Tracef("set unit %q resource %q progress", unitID, res.Name)
  1022  	if res.PendingID == "" && progress != nil {
  1023  		return errors.Errorf("only pending resources may track progress")
  1024  	}
  1025  	stored, err := p.getStored(res)
  1026  	if err != nil {
  1027  		return errors.Trace(err)
  1028  	}
  1029  	// TODO(ericsnow) Ensure that stored.Resource matches res? If we do
  1030  	// so then the following line is unnecessary.
  1031  	stored.Resource = res
  1032  
  1033  	if err := res.Validate(); err != nil {
  1034  		return errors.Annotate(err, "bad resource")
  1035  	}
  1036  
  1037  	buildTxn := func(attempt int) ([]txn.Op, error) {
  1038  		// This is an "upsert".
  1039  		var ops []txn.Op
  1040  		switch attempt {
  1041  		case 0:
  1042  			ops = newInsertUnitResourceOps(unitID, stored, progress)
  1043  		case 1:
  1044  			ops = newUpdateUnitResourceOps(unitID, stored, progress)
  1045  		default:
  1046  			// Either insert or update will work so we should not get here.
  1047  			return nil, errors.New("setting the resource failed")
  1048  		}
  1049  		// No pending resources so we always do this here.
  1050  		ops = append(ops, applicationExistsOps(res.ApplicationID)...)
  1051  		return ops, nil
  1052  	}
  1053  	if err := p.st.db().Run(buildTxn); err != nil {
  1054  		return errors.Trace(err)
  1055  	}
  1056  	return nil
  1057  }
  1058  
  1059  // RemovePendingAppResources removes the pending application-level
  1060  // resources for a specific application, normally in the case that the
  1061  // application couldn't be deployed.
  1062  func (p *resourcePersistence) RemovePendingAppResources(applicationID string, pendingIDs map[string]string) error {
  1063  	buildTxn := func(int) ([]txn.Op, error) {
  1064  		return p.removePendingAppResourcesOps(applicationID, pendingIDs)
  1065  	}
  1066  	return errors.Trace(p.st.db().Run(buildTxn))
  1067  }
  1068  
  1069  // resolveApplicationPendingResourcesOps generates mongo transaction operations
  1070  // to set the identified resources as active.
  1071  func (p *resourcePersistence) resolveApplicationPendingResourcesOps(applicationID string, pendingIDs map[string]string) ([]txn.Op, error) {
  1072  	rLogger.Tracef("resolve pending resource ops for %q", applicationID)
  1073  	if len(pendingIDs) == 0 {
  1074  		return nil, nil
  1075  	}
  1076  
  1077  	// TODO(ericsnow) The resources need to be pulled in from the charm
  1078  	// store before we get to this point.
  1079  
  1080  	var allOps []txn.Op
  1081  	for name, pendingID := range pendingIDs {
  1082  		resID := newAppResourceID(applicationID, name)
  1083  		ops, err := p.resolvePendingResourceOps(resID, pendingID)
  1084  		if err != nil {
  1085  			return nil, errors.Trace(err)
  1086  		}
  1087  		allOps = append(allOps, ops...)
  1088  	}
  1089  	return allOps, nil
  1090  }
  1091  
  1092  // resolvePendingResourceOps generates mongo transaction operations
  1093  // to set the identified resource as active.
  1094  func (p *resourcePersistence) resolvePendingResourceOps(resID, pendingID string) ([]txn.Op, error) {
  1095  	rLogger.Tracef("resolve pending resource ops %q, %q", resID, pendingID)
  1096  	if pendingID == "" {
  1097  		return nil, errors.New("missing pending ID")
  1098  	}
  1099  
  1100  	oldDoc, err := p.getOnePending(resID, pendingID)
  1101  	if errors.IsNotFound(err) {
  1102  		return nil, errors.NotFoundf("pending resource %q (%s)", resID, pendingID)
  1103  	}
  1104  	if err != nil {
  1105  		return nil, errors.Trace(err)
  1106  	}
  1107  	pending, err := doc2resource(oldDoc)
  1108  	if err != nil {
  1109  		return nil, errors.Trace(err)
  1110  	}
  1111  
  1112  	exists := true
  1113  	if _, err := p.getOne(resID); errors.IsNotFound(err) {
  1114  		exists = false
  1115  	} else if err != nil {
  1116  		return nil, errors.Trace(err)
  1117  	}
  1118  
  1119  	csExists := true
  1120  	csResID := resID + resourcesCharmstoreIDSuffix
  1121  	if _, err := p.getOne(csResID); errors.IsNotFound(err) {
  1122  		csExists = false
  1123  	} else if err != nil {
  1124  		return nil, errors.Trace(err)
  1125  	}
  1126  
  1127  	ops := newResolvePendingResourceOps(pending, exists, csExists)
  1128  	return ops, nil
  1129  }
  1130  
  1131  // removeUnitResourcesOps returns mgo transaction operations
  1132  // that remove resource information specific to the unit from state.
  1133  func (p *resourcePersistence) removeUnitResourcesOps(unitID string) ([]txn.Op, error) {
  1134  	docs, err := p.unitResources(unitID)
  1135  	if err != nil {
  1136  		return nil, errors.Trace(err)
  1137  	}
  1138  
  1139  	ops := newRemoveResourcesOps(docs)
  1140  	// We do not remove the resource from the blob store here. That is
  1141  	// an application-level matter.
  1142  	return ops, nil
  1143  }
  1144  
  1145  // removeResourcesOps returns mgo transaction operations that
  1146  // remove all the application's resources from state.
  1147  func (p *resourcePersistence) removeResourcesOps(applicationID string) ([]txn.Op, error) {
  1148  	docs, err := p.resources(applicationID)
  1149  	if err != nil {
  1150  		return nil, errors.Trace(err)
  1151  	}
  1152  	return removeResourcesAndStorageCleanupOps(docs), nil
  1153  }
  1154  
  1155  // removePendingAppResourcesOps returns mgo transaction operations to
  1156  // clean up pending resources for the application from state. We pass
  1157  // in the pending IDs to avoid removing the wrong resources if there's
  1158  // a race to deploy the same application.
  1159  func (p *resourcePersistence) removePendingAppResourcesOps(applicationID string, pendingIDs map[string]string) ([]txn.Op, error) {
  1160  	docs, err := p.resources(applicationID)
  1161  	if err != nil {
  1162  		return nil, errors.Trace(err)
  1163  	}
  1164  	pending := make([]resourceDoc, 0, len(docs))
  1165  	for _, doc := range docs {
  1166  		if doc.UnitID != "" || doc.PendingID == "" {
  1167  			continue
  1168  		}
  1169  		if pendingIDs[doc.Name] != doc.PendingID {
  1170  			// This is a pending resource for a different deployment
  1171  			// of an application with the same name.
  1172  			continue
  1173  		}
  1174  		pending = append(pending, doc)
  1175  	}
  1176  	return removeResourcesAndStorageCleanupOps(pending), nil
  1177  }
  1178  
  1179  func applicationExistsOps(applicationID string) []txn.Op {
  1180  	return []txn.Op{{
  1181  		C:      applicationsC,
  1182  		Id:     applicationID,
  1183  		Assert: isAliveDoc,
  1184  	}}
  1185  }
  1186  
  1187  func removeResourcesAndStorageCleanupOps(docs []resourceDoc) []txn.Op {
  1188  	ops := newRemoveResourcesOps(docs)
  1189  	seenPaths := set.NewStrings()
  1190  	for _, doc := range docs {
  1191  		// Don't schedule cleanups for placeholder resources, or multiple for a given path.
  1192  		if doc.StoragePath == "" || seenPaths.Contains(doc.StoragePath) {
  1193  			continue
  1194  		}
  1195  		ops = append(ops, newCleanupOp(cleanupResourceBlob, doc.StoragePath))
  1196  		seenPaths.Add(doc.StoragePath)
  1197  	}
  1198  	return ops
  1199  }
  1200  
  1201  func newInsertStagedResourceOps(stored storedResource) []txn.Op {
  1202  	doc := newStagedResourceDoc(stored)
  1203  
  1204  	return []txn.Op{{
  1205  		C:      resourcesC,
  1206  		Id:     doc.DocID,
  1207  		Assert: txn.DocMissing,
  1208  		Insert: doc,
  1209  	}}
  1210  }
  1211  
  1212  func newEnsureStagedResourceSameOps(stored storedResource) []txn.Op {
  1213  	doc := newStagedResourceDoc(stored)
  1214  
  1215  	// Other than cause the txn to abort, we don't do anything here.
  1216  	return []txn.Op{{
  1217  		C:      resourcesC,
  1218  		Id:     doc.DocID,
  1219  		Assert: doc, // TODO(ericsnow) Is this okay?
  1220  	}}
  1221  }
  1222  
  1223  func newRemoveStagedResourceOps(id string) []txn.Op {
  1224  	fullID := stagedResourceID(id)
  1225  
  1226  	// We don't assert that it exists. We want "missing" to be a noop.
  1227  	return []txn.Op{{
  1228  		C:      resourcesC,
  1229  		Id:     fullID,
  1230  		Remove: true,
  1231  	}}
  1232  }
  1233  
  1234  func newInsertResourceOps(stored storedResource) []txn.Op {
  1235  	doc := newResourceDoc(stored)
  1236  
  1237  	return []txn.Op{{
  1238  		C:      resourcesC,
  1239  		Id:     doc.DocID,
  1240  		Assert: txn.DocMissing,
  1241  		Insert: doc,
  1242  	}}
  1243  }
  1244  
  1245  func resourceDocToUpdateOp(doc *resourceDoc) bson.M {
  1246  	// Note (jam 2018-07-12): What are we actually allowed to update?
  1247  	// The old code was trying to delete the doc and replace it entirely, so for now we'll just set everything except
  1248  	// the doc's own id, which is clearly not allowed to change.
  1249  	return bson.M{"$set": bson.M{
  1250  		"resource-id":                doc.ID,
  1251  		"pending-id":                 doc.PendingID,
  1252  		"application-id":             doc.ApplicationID,
  1253  		"unit-id":                    doc.UnitID,
  1254  		"name":                       doc.Name,
  1255  		"type":                       doc.Type,
  1256  		"path":                       doc.Path,
  1257  		"description":                doc.Description,
  1258  		"origin":                     doc.Origin,
  1259  		"revision":                   doc.Revision,
  1260  		"fingerprint":                doc.Fingerprint,
  1261  		"size":                       doc.Size,
  1262  		"username":                   doc.Username,
  1263  		"timestamp-when-added":       doc.Timestamp,
  1264  		"storage-path":               doc.StoragePath,
  1265  		"download-progress":          doc.DownloadProgress,
  1266  		"timestamp-when-last-polled": doc.LastPolled.Round(time.Second).UTC(),
  1267  	}}
  1268  }
  1269  
  1270  func newUpdateResourceOps(stored storedResource) []txn.Op {
  1271  	doc := newResourceDoc(stored)
  1272  
  1273  	rLogger.Tracef("updating resource %s to %# v", stored.ID, pretty.Formatter(doc))
  1274  	return []txn.Op{{
  1275  		C:      resourcesC,
  1276  		Id:     doc.DocID,
  1277  		Assert: txn.DocExists,
  1278  		Update: resourceDocToUpdateOp(doc),
  1279  	}}
  1280  }
  1281  
  1282  func newInsertCharmStoreResourceOps(res charmStoreResource) []txn.Op {
  1283  	doc := newCharmStoreResourceDoc(res)
  1284  
  1285  	return []txn.Op{{
  1286  		C:      resourcesC,
  1287  		Id:     doc.DocID,
  1288  		Assert: txn.DocMissing,
  1289  		Insert: doc,
  1290  	}}
  1291  }
  1292  
  1293  func newUpdateCharmStoreResourceOps(res charmStoreResource) []txn.Op {
  1294  	doc := newCharmStoreResourceDoc(res)
  1295  
  1296  	if rLogger.IsTraceEnabled() {
  1297  		rLogger.Tracef("updating charm store resource %s to %# v", res.id, pretty.Formatter(doc))
  1298  	}
  1299  	return []txn.Op{{
  1300  		C:      resourcesC,
  1301  		Id:     doc.DocID,
  1302  		Assert: txn.DocExists,
  1303  		Update: resourceDocToUpdateOp(doc),
  1304  	}}
  1305  }
  1306  
  1307  func newInsertUnitResourceOps(unitID string, stored storedResource, progress *int64) []txn.Op {
  1308  	doc := newUnitResourceDoc(unitID, stored)
  1309  	doc.DownloadProgress = progress
  1310  
  1311  	return []txn.Op{{
  1312  		C:      resourcesC,
  1313  		Id:     doc.DocID,
  1314  		Assert: txn.DocMissing,
  1315  		Insert: doc,
  1316  	}}
  1317  }
  1318  
  1319  func newUpdateUnitResourceOps(unitID string, stored storedResource, progress *int64) []txn.Op {
  1320  	doc := newUnitResourceDoc(unitID, stored)
  1321  	doc.DownloadProgress = progress
  1322  
  1323  	if rLogger.IsTraceEnabled() {
  1324  		rLogger.Tracef("updating unit resource %s to %# v", unitID, pretty.Formatter(doc))
  1325  	}
  1326  	return []txn.Op{{
  1327  		C:      resourcesC,
  1328  		Id:     doc.DocID,
  1329  		Assert: txn.DocExists, // feels like we need more
  1330  		Update: resourceDocToUpdateOp(doc),
  1331  	}}
  1332  }
  1333  
  1334  func newRemoveResourcesOps(docs []resourceDoc) []txn.Op {
  1335  	// The likelihood of a race is small and the consequences are minor,
  1336  	// so we don't worry about the corner case of missing a doc here.
  1337  	var ops []txn.Op
  1338  	for _, doc := range docs {
  1339  		// We do not bother to assert txn.DocExists since it will be
  1340  		// gone either way, which is the result we're after.
  1341  		ops = append(ops, txn.Op{
  1342  			C:      resourcesC,
  1343  			Id:     doc.DocID,
  1344  			Remove: true,
  1345  		})
  1346  	}
  1347  	return ops
  1348  }
  1349  
  1350  // newResolvePendingResourceOps generates transaction operations that
  1351  // will resolve a pending resource doc and make it active.
  1352  //
  1353  // We trust that the provided resource really is pending
  1354  // and that it matches the existing doc with the same ID.
  1355  func newResolvePendingResourceOps(pending storedResource, exists, csExists bool) []txn.Op {
  1356  	oldID := pendingResourceID(pending.ID, pending.PendingID)
  1357  	newRes := pending
  1358  	newRes.PendingID = ""
  1359  	// TODO(ericsnow) Update newRes.StoragePath? Doing so would require
  1360  	// moving the resource in the blobstore to the correct path, which
  1361  	// we cannot do in the transaction...
  1362  	ops := []txn.Op{{
  1363  		C:      resourcesC,
  1364  		Id:     oldID,
  1365  		Assert: txn.DocExists,
  1366  		Remove: true,
  1367  	}}
  1368  
  1369  	// TODO(perrito666) 2016-05-02 lp:1558657
  1370  	csRes := charmStoreResource{
  1371  		Resource:      newRes.Resource.Resource,
  1372  		id:            newRes.ID,
  1373  		applicationID: newRes.ApplicationID,
  1374  		// Truncate the time to remove monotonic time for Go 1.9+
  1375  		// to make it easier for tests to compare the time.
  1376  		lastPolled: time.Now().Truncate(1).UTC(),
  1377  	}
  1378  
  1379  	if exists {
  1380  		ops = append(ops, newUpdateResourceOps(newRes)...)
  1381  
  1382  	} else {
  1383  		ops = append(ops, newInsertResourceOps(newRes)...)
  1384  	}
  1385  	if csExists {
  1386  		return append(ops, newUpdateCharmStoreResourceOps(csRes)...)
  1387  	} else {
  1388  		return append(ops, newInsertCharmStoreResourceOps(csRes)...)
  1389  	}
  1390  }