github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/payload/persistence/unit.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package persistence
     5  
     6  // TODO(ericsnow) Eliminate the mongo-related imports here.
     7  
     8  import (
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	jujutxn "github.com/juju/txn"
    12  	"gopkg.in/mgo.v2/txn"
    13  
    14  	"github.com/juju/juju/payload"
    15  )
    16  
    17  var logger = loggo.GetLogger("juju.payload.persistence")
    18  
    19  // TODO(ericsnow) Merge Persistence and EnvPersistence.
    20  
    21  // TODO(ericsnow) Store status in the status collection?
    22  
    23  // TODO(ericsnow) Implement persistence using a TXN abstraction (used
    24  // in the business logic) with ops factories available from the
    25  // persistence layer.
    26  
    27  // TODO(ericsnow) Move PersistenceBase to the components package?
    28  
    29  // PersistenceBase exposes the core persistence functionality needed
    30  // for payloads.
    31  type PersistenceBase interface {
    32  	// One populates doc with the document corresponding to the given
    33  	// ID. Missing documents result in errors.NotFound.
    34  	One(collName, id string, doc interface{}) error
    35  	// All populates docs with the list of the documents corresponding
    36  	// to the provided query.
    37  	All(collName string, query, docs interface{}) error
    38  	// Run runs the transaction generated by the provided factory
    39  	// function. It may be retried several times.
    40  	Run(transactions jujutxn.TransactionSource) error
    41  }
    42  
    43  // Persistence exposes the high-level persistence functionality
    44  // related to payloads in Juju.
    45  type Persistence struct {
    46  	st   PersistenceBase
    47  	unit string
    48  }
    49  
    50  // NewPersistence builds a new Persistence based on the provided info.
    51  func NewPersistence(st PersistenceBase, unit string) *Persistence {
    52  	return &Persistence{
    53  		st:   st,
    54  		unit: unit,
    55  	}
    56  }
    57  
    58  // Track adds records for the payload to persistence. If the payload
    59  // is already there then false gets returned (true if inserted).
    60  // Existing records are not checked for consistency.
    61  func (pp Persistence) Track(id string, pl payload.Payload) (bool, error) {
    62  	logger.Tracef("insertng %#v", pl)
    63  
    64  	_, err := pp.LookUp(pl.Name, pl.ID)
    65  	if err == nil {
    66  		return false, errors.AlreadyExistsf("payload for %q", pl.FullID())
    67  	} else if !errors.IsNotFound(err) {
    68  		return false, errors.Annotate(err, "while checking for collisions")
    69  	}
    70  	// TODO(ericsnow) There is a *slight* race here. I haven't found
    71  	// a simple way to check the secondary key in the transaction.
    72  
    73  	var okay bool
    74  	var ops []txn.Op
    75  	// TODO(ericsnow) Add unitPersistence.newEnsureAliveOp(pp.unit)?
    76  	ops = append(ops, pp.newInsertPayloadOps(id, pl)...)
    77  	buildTxn := func(attempt int) ([]txn.Op, error) {
    78  		if attempt > 0 {
    79  			okay = false
    80  			return nil, jujutxn.ErrNoOperations
    81  		}
    82  		okay = true
    83  		return ops, nil
    84  	}
    85  	if err := pp.st.Run(buildTxn); err != nil {
    86  		return false, errors.Trace(err)
    87  	}
    88  	return okay, nil
    89  }
    90  
    91  // SetStatus updates the raw status for the identified payload in
    92  // persistence. The return value corresponds to whether or not the
    93  // record was found in persistence. Any other problem results in
    94  // an error. The payload is not checked for inconsistent records.
    95  func (pp Persistence) SetStatus(id, status string) (bool, error) {
    96  	logger.Tracef("setting status for %q", id)
    97  
    98  	var found bool
    99  	var ops []txn.Op
   100  	// TODO(ericsnow) Add unitPersistence.newEnsureAliveOp(pp.unit)?
   101  	ops = append(ops, pp.newSetRawStatusOps(id, status)...)
   102  	buildTxn := func(attempt int) ([]txn.Op, error) {
   103  		if attempt > 0 {
   104  			found = false
   105  			return nil, jujutxn.ErrNoOperations
   106  		}
   107  		found = true
   108  		return ops, nil
   109  	}
   110  	if err := pp.st.Run(buildTxn); err != nil {
   111  		return false, errors.Trace(err)
   112  	}
   113  	return found, nil
   114  }
   115  
   116  // List builds the list of payloads found in persistence which match
   117  // the provided IDs. The lists of IDs with missing records is also
   118  // returned.
   119  func (pp Persistence) List(ids ...string) ([]payload.Payload, []string, error) {
   120  	// TODO(ericsnow) Ensure that the unit is Alive?
   121  
   122  	docs, err := pp.payloads(ids)
   123  	if err != nil {
   124  		return nil, nil, errors.Trace(err)
   125  	}
   126  
   127  	var results []payload.Payload
   128  	var missing []string
   129  	for _, id := range ids {
   130  		p, ok := pp.extractPayload(id, docs)
   131  		if !ok {
   132  			missing = append(missing, id)
   133  			continue
   134  		}
   135  		results = append(results, *p)
   136  	}
   137  	return results, missing, nil
   138  }
   139  
   140  // ListAll builds the list of all payloads found in persistence.
   141  // Inconsistent records result in errors.NotValid.
   142  func (pp Persistence) ListAll() ([]payload.Payload, error) {
   143  	// TODO(ericsnow) Ensure that the unit is Alive?
   144  
   145  	docs, err := pp.allPayloads()
   146  	if err != nil {
   147  		return nil, errors.Trace(err)
   148  	}
   149  
   150  	var results []payload.Payload
   151  	for id := range docs {
   152  		p, _ := pp.extractPayload(id, docs)
   153  		results = append(results, *p)
   154  	}
   155  	return results, nil
   156  }
   157  
   158  // LookUp returns the payload ID for the given name/rawID pair.
   159  func (pp Persistence) LookUp(name, rawID string) (string, error) {
   160  	// TODO(ericsnow) This could be more efficient.
   161  
   162  	docs, err := pp.allPayloads()
   163  	if err != nil {
   164  		return "", errors.Trace(err)
   165  	}
   166  
   167  	for id, doc := range docs {
   168  		if doc.match(name, rawID) {
   169  			return id, nil
   170  		}
   171  	}
   172  
   173  	return "", errors.NotFoundf("payload for %s/%s", name, rawID)
   174  }
   175  
   176  // TODO(ericsnow) Add payloads to state/cleanup.go.
   177  
   178  // TODO(ericsnow) How to ensure they are completely removed from state
   179  // (when you factor in status stored in a separate collection)?
   180  
   181  // Untrack removes all records associated with the identified payload
   182  // from persistence. Also returned is whether or not the payload was
   183  // found. If the records for the payload are not consistent then
   184  // errors.NotValid is returned.
   185  func (pp Persistence) Untrack(id string) (bool, error) {
   186  	var found bool
   187  	var ops []txn.Op
   188  	// TODO(ericsnow) Add unitPersistence.newEnsureAliveOp(pp.unit)?
   189  	ops = append(ops, pp.newRemovePayloadOps(id)...)
   190  	buildTxn := func(attempt int) ([]txn.Op, error) {
   191  		if attempt > 0 {
   192  			found = false
   193  			return nil, jujutxn.ErrNoOperations
   194  		}
   195  		found = true
   196  		return ops, nil
   197  	}
   198  	if err := pp.st.Run(buildTxn); err != nil {
   199  		return false, errors.Trace(err)
   200  	}
   201  	return found, nil
   202  }