github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/resources_persistence.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  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	jujutxn "github.com/juju/txn"
    11  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    12  	"gopkg.in/juju/names.v2"
    13  	"gopkg.in/mgo.v2/txn"
    14  
    15  	"github.com/juju/juju/resource"
    16  )
    17  
    18  const (
    19  	// CleanupKindResourceBlob identifies the cleanup kind
    20  	// for resource blobs.
    21  	CleanupKindResourceBlob = "resourceBlob"
    22  )
    23  
    24  // ResourcePersistenceBase exposes the core persistence functionality
    25  // needed for resources.
    26  type ResourcePersistenceBase interface {
    27  	// One populates doc with the document corresponding to the given
    28  	// ID. Missing documents result in errors.NotFound.
    29  	One(collName, id string, doc interface{}) error
    30  
    31  	// All populates docs with the list of the documents corresponding
    32  	// to the provided query.
    33  	All(collName string, query, docs interface{}) error
    34  
    35  	// Run runs the transaction generated by the provided factory
    36  	// function. It may be retried several times.
    37  	Run(transactions jujutxn.TransactionSource) error
    38  
    39  	// ApplicationExistsOps returns the operations that verify that the
    40  	// identified application exists.
    41  	ApplicationExistsOps(applicationID string) []txn.Op
    42  
    43  	// IncCharmModifiedVersionOps returns the operations necessary to increment
    44  	// the CharmModifiedVersion field for the given application.
    45  	IncCharmModifiedVersionOps(applicationID string) []txn.Op
    46  
    47  	// NewCleanupOp creates a mgo transaction operation that queues up
    48  	// some cleanup action in state.
    49  	NewCleanupOp(kind, prefix string) txn.Op
    50  }
    51  
    52  // ResourcePersistence provides the persistence functionality for the
    53  // Juju environment as a whole.
    54  type ResourcePersistence struct {
    55  	base ResourcePersistenceBase
    56  }
    57  
    58  // NewResourcePersistence wraps the base in a new ResourcePersistence.
    59  func NewResourcePersistence(base ResourcePersistenceBase) *ResourcePersistence {
    60  	return &ResourcePersistence{
    61  		base: base,
    62  	}
    63  }
    64  
    65  // ListResources returns the info for each non-pending resource of the
    66  // identified service.
    67  func (p ResourcePersistence) ListResources(applicationID string) (resource.ServiceResources, error) {
    68  	logger.Tracef("listing all resources for application %q", applicationID)
    69  
    70  	docs, err := p.resources(applicationID)
    71  	if err != nil {
    72  		return resource.ServiceResources{}, errors.Trace(err)
    73  	}
    74  
    75  	store := map[string]charmresource.Resource{}
    76  	units := map[names.UnitTag][]resource.Resource{}
    77  	downloadProgress := make(map[names.UnitTag]map[string]int64)
    78  
    79  	var results resource.ServiceResources
    80  	for _, doc := range docs {
    81  		if doc.PendingID != "" {
    82  			continue
    83  		}
    84  
    85  		res, err := doc2basicResource(doc)
    86  		if err != nil {
    87  			return resource.ServiceResources{}, errors.Trace(err)
    88  		}
    89  		if !doc.LastPolled.IsZero() {
    90  			store[res.Name] = res.Resource
    91  			continue
    92  		}
    93  		if doc.UnitID == "" {
    94  			results.Resources = append(results.Resources, res)
    95  			continue
    96  		}
    97  		tag := names.NewUnitTag(doc.UnitID)
    98  		if doc.PendingID == "" {
    99  			units[tag] = append(units[tag], res)
   100  		}
   101  		if doc.DownloadProgress != nil {
   102  			if downloadProgress[tag] == nil {
   103  				downloadProgress[tag] = make(map[string]int64)
   104  			}
   105  			downloadProgress[tag][doc.Name] = *doc.DownloadProgress
   106  		}
   107  	}
   108  	for _, res := range results.Resources {
   109  		storeRes := store[res.Name]
   110  		results.CharmStoreResources = append(results.CharmStoreResources, storeRes)
   111  	}
   112  	for tag, res := range units {
   113  		results.UnitResources = append(results.UnitResources, resource.UnitResources{
   114  			Tag:              tag,
   115  			Resources:        res,
   116  			DownloadProgress: downloadProgress[tag],
   117  		})
   118  	}
   119  	return results, nil
   120  }
   121  
   122  // ListPendingResources returns the extended, model-related info for
   123  // each pending resource of the identifies service.
   124  func (p ResourcePersistence) ListPendingResources(applicationID string) ([]resource.Resource, error) {
   125  	docs, err := p.resources(applicationID)
   126  	if err != nil {
   127  		return nil, errors.Trace(err)
   128  	}
   129  
   130  	var resources []resource.Resource
   131  	for _, doc := range docs {
   132  		if doc.PendingID == "" {
   133  			continue
   134  		}
   135  		// doc.UnitID will always be empty here.
   136  
   137  		res, err := doc2basicResource(doc)
   138  		if err != nil {
   139  			return nil, errors.Trace(err)
   140  		}
   141  		resources = append(resources, res)
   142  	}
   143  	return resources, nil
   144  }
   145  
   146  // GetResource returns the extended, model-related info for the non-pending
   147  // resource.
   148  func (p ResourcePersistence) GetResource(id string) (res resource.Resource, storagePath string, _ error) {
   149  	doc, err := p.getOne(id)
   150  	if err != nil {
   151  		return res, "", errors.Trace(err)
   152  	}
   153  
   154  	stored, err := doc2resource(doc)
   155  	if err != nil {
   156  		return res, "", errors.Trace(err)
   157  	}
   158  
   159  	return stored.Resource, stored.storagePath, nil
   160  }
   161  
   162  // StageResource adds the resource in a separate staging area
   163  // if the resource isn't already staged. If it is then
   164  // errors.AlreadyExists is returned. A wrapper around the staged
   165  // resource is returned which supports both finalizing and removing
   166  // the staged resource.
   167  func (p ResourcePersistence) StageResource(res resource.Resource, storagePath string) (*StagedResource, error) {
   168  	if storagePath == "" {
   169  		return nil, errors.Errorf("missing storage path")
   170  	}
   171  
   172  	if err := res.Validate(); err != nil {
   173  		return nil, errors.Annotate(err, "bad resource")
   174  	}
   175  
   176  	stored := storedResource{
   177  		Resource:    res,
   178  		storagePath: storagePath,
   179  	}
   180  	staged := &StagedResource{
   181  		base:   p.base,
   182  		id:     res.ID,
   183  		stored: stored,
   184  	}
   185  	if err := staged.stage(); err != nil {
   186  		return nil, errors.Trace(err)
   187  	}
   188  	return staged, nil
   189  }
   190  
   191  // SetResource sets the info for the resource.
   192  func (p ResourcePersistence) SetResource(res resource.Resource) error {
   193  	stored, err := p.getStored(res)
   194  	if errors.IsNotFound(err) {
   195  		stored = storedResource{Resource: res}
   196  	} else if err != nil {
   197  		return errors.Trace(err)
   198  	}
   199  	// TODO(ericsnow) Ensure that stored.Resource matches res? If we do
   200  	// so then the following line is unnecessary.
   201  	stored.Resource = res
   202  
   203  	if err := res.Validate(); err != nil {
   204  		return errors.Annotate(err, "bad resource")
   205  	}
   206  
   207  	buildTxn := func(attempt int) ([]txn.Op, error) {
   208  		// This is an "upsert".
   209  		var ops []txn.Op
   210  		switch attempt {
   211  		case 0:
   212  			ops = newInsertResourceOps(stored)
   213  		case 1:
   214  			ops = newUpdateResourceOps(stored)
   215  		default:
   216  			// Either insert or update will work so we should not get here.
   217  			return nil, errors.New("setting the resource failed")
   218  		}
   219  		if stored.PendingID == "" {
   220  			// Only non-pending resources must have an existing service.
   221  			ops = append(ops, p.base.ApplicationExistsOps(res.ApplicationID)...)
   222  		}
   223  		return ops, nil
   224  	}
   225  	if err := p.base.Run(buildTxn); err != nil {
   226  		return errors.Trace(err)
   227  	}
   228  	return nil
   229  }
   230  
   231  // SetCharmStoreResource stores the resource info that was retrieved
   232  // from the charm store.
   233  func (p ResourcePersistence) SetCharmStoreResource(id, applicationID string, res charmresource.Resource, lastPolled time.Time) error {
   234  	if err := res.Validate(); err != nil {
   235  		return errors.Annotate(err, "bad resource")
   236  	}
   237  
   238  	csRes := charmStoreResource{
   239  		Resource:      res,
   240  		id:            id,
   241  		applicationID: applicationID,
   242  		lastPolled:    lastPolled,
   243  	}
   244  
   245  	buildTxn := func(attempt int) ([]txn.Op, error) {
   246  		// This is an "upsert".
   247  		var ops []txn.Op
   248  		switch attempt {
   249  		case 0:
   250  			ops = newInsertCharmStoreResourceOps(csRes)
   251  		case 1:
   252  			ops = newUpdateCharmStoreResourceOps(csRes)
   253  		default:
   254  			// Either insert or update will work so we should not get here.
   255  			return nil, errors.New("setting the resource failed")
   256  		}
   257  		// No pending resources so we always do this here.
   258  		ops = append(ops, p.base.ApplicationExistsOps(applicationID)...)
   259  		return ops, nil
   260  	}
   261  	if err := p.base.Run(buildTxn); err != nil {
   262  		return errors.Trace(err)
   263  	}
   264  	return nil
   265  }
   266  
   267  // SetUnitResource stores the resource info for a particular unit. The
   268  // resource must already be set for the application.
   269  func (p ResourcePersistence) SetUnitResource(unitID string, res resource.Resource) error {
   270  	if res.PendingID != "" {
   271  		return errors.Errorf("pending resources not allowed")
   272  	}
   273  	return p.setUnitResource(unitID, res, nil)
   274  }
   275  
   276  // SetUnitResource stores the resource info for a particular unit. The
   277  // resource must already be set for the application. The provided progress
   278  // is stored in the DB.
   279  func (p ResourcePersistence) SetUnitResourceProgress(unitID string, res resource.Resource, progress int64) error {
   280  	if res.PendingID == "" {
   281  		return errors.Errorf("only pending resources may track progress")
   282  	}
   283  	return p.setUnitResource(unitID, res, &progress)
   284  }
   285  
   286  func (p ResourcePersistence) setUnitResource(unitID string, res resource.Resource, progress *int64) error {
   287  	stored, err := p.getStored(res)
   288  	if err != nil {
   289  		return errors.Trace(err)
   290  	}
   291  	// TODO(ericsnow) Ensure that stored.Resource matches res? If we do
   292  	// so then the following line is unnecessary.
   293  	stored.Resource = res
   294  
   295  	if err := res.Validate(); err != nil {
   296  		return errors.Annotate(err, "bad resource")
   297  	}
   298  
   299  	buildTxn := func(attempt int) ([]txn.Op, error) {
   300  		// This is an "upsert".
   301  		var ops []txn.Op
   302  		switch attempt {
   303  		case 0:
   304  			ops = newInsertUnitResourceOps(unitID, stored, progress)
   305  		case 1:
   306  			ops = newUpdateUnitResourceOps(unitID, stored, progress)
   307  		default:
   308  			// Either insert or update will work so we should not get here.
   309  			return nil, errors.New("setting the resource failed")
   310  		}
   311  		// No pending resources so we always do this here.
   312  		ops = append(ops, p.base.ApplicationExistsOps(res.ApplicationID)...)
   313  		return ops, nil
   314  	}
   315  	if err := p.base.Run(buildTxn); err != nil {
   316  		return errors.Trace(err)
   317  	}
   318  	return nil
   319  }
   320  
   321  func (p ResourcePersistence) getStored(res resource.Resource) (storedResource, error) {
   322  	doc, err := p.getOne(res.ID)
   323  	if errors.IsNotFound(err) {
   324  		err = errors.NotFoundf("resource %q", res.Name)
   325  	}
   326  	if err != nil {
   327  		return storedResource{}, errors.Trace(err)
   328  	}
   329  
   330  	stored, err := doc2resource(doc)
   331  	if err != nil {
   332  		return stored, errors.Trace(err)
   333  	}
   334  
   335  	return stored, nil
   336  }
   337  
   338  // NewResolvePendingResourceOps generates mongo transaction operations
   339  // to set the identified resource as active.
   340  //
   341  // Leaking mongo details (transaction ops) is a necessary evil since we
   342  // do not have any machinery to facilitate transactions between
   343  // different components.
   344  func (p ResourcePersistence) NewResolvePendingResourceOps(resID, pendingID string) ([]txn.Op, error) {
   345  	if pendingID == "" {
   346  		return nil, errors.New("missing pending ID")
   347  	}
   348  
   349  	oldDoc, err := p.getOnePending(resID, pendingID)
   350  	if errors.IsNotFound(err) {
   351  		return nil, errors.NotFoundf("pending resource %q (%s)", resID, pendingID)
   352  	}
   353  	if err != nil {
   354  		return nil, errors.Trace(err)
   355  	}
   356  	pending, err := doc2resource(oldDoc)
   357  	if err != nil {
   358  		return nil, errors.Trace(err)
   359  	}
   360  
   361  	exists := true
   362  	if _, err := p.getOne(resID); errors.IsNotFound(err) {
   363  		exists = false
   364  	} else if err != nil {
   365  		return nil, errors.Trace(err)
   366  	}
   367  
   368  	ops := newResolvePendingResourceOps(pending, exists)
   369  	return ops, nil
   370  }
   371  
   372  // NewRemoveUnitResourcesOps returns mgo transaction operations
   373  // that remove resource information specific to the unit from state.
   374  func (p ResourcePersistence) NewRemoveUnitResourcesOps(unitID string) ([]txn.Op, error) {
   375  	docs, err := p.unitResources(unitID)
   376  	if err != nil {
   377  		return nil, errors.Trace(err)
   378  	}
   379  
   380  	ops := newRemoveResourcesOps(docs)
   381  	// We do not remove the resource from the blob store here. That is
   382  	// an application-level matter.
   383  	return ops, nil
   384  }
   385  
   386  // NewRemoveResourcesOps returns mgo transaction operations that
   387  // remove all the service's resources from state.
   388  func (p ResourcePersistence) NewRemoveResourcesOps(applicationID string) ([]txn.Op, error) {
   389  	docs, err := p.resources(applicationID)
   390  	if err != nil {
   391  		return nil, errors.Trace(err)
   392  	}
   393  
   394  	ops := newRemoveResourcesOps(docs)
   395  	for _, doc := range docs {
   396  		ops = append(ops, p.base.NewCleanupOp(CleanupKindResourceBlob, doc.StoragePath))
   397  	}
   398  	return ops, nil
   399  }