github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/life_ns.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/mgo.v2"
     9  	"gopkg.in/mgo.v2/bson"
    10  	"gopkg.in/mgo.v2/txn"
    11  
    12  	"github.com/juju/juju/mongo"
    13  )
    14  
    15  // nsLife_ backs nsLife.
    16  type nsLife_ struct{}
    17  
    18  // nsLife namespaces low-level entity-life functionality. See the
    19  // discussion in nsPayloads: this exists not to be the one place for
    20  // life functionality (that would be a huge change), but to at least
    21  // represent the parts we need for payloads in a consistent fashion.
    22  //
    23  // Both the namespacing and the explicit Collection->op approach seem
    24  // to be good ideas, and should ideally be extended as we continue.
    25  var nsLife = nsLife_{}
    26  
    27  // destroyOp returns errNotAlive if the identified entity is not Alive;
    28  // or a txn.Op that will fail if the condition no longer holds, and
    29  // otherwise set Life to Dying and make any other updates supplied in
    30  // update.
    31  func (nsLife_) destroyOp(entities mongo.Collection, docID string, update bson.D) (txn.Op, error) {
    32  	op, err := nsLife.aliveOp(entities, docID)
    33  	if err != nil {
    34  		return txn.Op{}, errors.Trace(err)
    35  	}
    36  	setDying := bson.D{{"$set", bson.D{{"life", Dying}}}}
    37  	op.Update = append(setDying, update...)
    38  	return op, nil
    39  }
    40  
    41  // dieOp returns errNotDying if the identified entity is Alive, or
    42  // errAlreadyDead if the entity is Dead or gone; or a txn.Op that will
    43  // fail if the condition no longer holds, and otherwise set Life to
    44  // Dead, and make any other updates supplied in update.
    45  func (nsLife_) dieOp(entities mongo.Collection, docID string, update bson.D) (txn.Op, error) {
    46  	life, err := nsLife.read(entities, docID)
    47  	if errors.IsNotFound(err) {
    48  		return txn.Op{}, errAlreadyDead
    49  	} else if err != nil {
    50  		return txn.Op{}, errors.Trace(err)
    51  	}
    52  	switch life {
    53  	case Alive:
    54  		return txn.Op{}, errNotDying
    55  	case Dead:
    56  		return txn.Op{}, errAlreadyDead
    57  	}
    58  	setDead := bson.D{{"$set", bson.D{{"life", Dead}}}}
    59  	return txn.Op{
    60  		C:      entities.Name(),
    61  		Id:     docID,
    62  		Assert: nsLife.dying(),
    63  		Update: append(setDead, update...),
    64  	}, nil
    65  }
    66  
    67  // aliveOp returns errNotAlive if the identified entity is not Alive; or
    68  // a txn.Op that will fail if the condition no longer holds.
    69  func (nsLife_) aliveOp(entities mongo.Collection, docID string) (txn.Op, error) {
    70  	op, err := nsLife.checkOp(entities, docID, nsLife.alive())
    71  	switch errors.Cause(err) {
    72  	case nil:
    73  	case errCheckFailed:
    74  		return txn.Op{}, errNotAlive
    75  	default:
    76  		return txn.Op{}, errors.Trace(err)
    77  	}
    78  	return op, nil
    79  }
    80  
    81  // dyingOp returns errNotDying if the identified entity is not Dying; or
    82  // a txn.Op that will fail if the condition no longer holds.
    83  func (nsLife_) dyingOp(entities mongo.Collection, docID string) (txn.Op, error) {
    84  	op, err := nsLife.checkOp(entities, docID, nsLife.dying())
    85  	switch errors.Cause(err) {
    86  	case nil:
    87  	case errCheckFailed:
    88  		return txn.Op{}, errNotDying
    89  	default:
    90  		return txn.Op{}, errors.Trace(err)
    91  	}
    92  	return op, nil
    93  }
    94  
    95  // notDeadOp returns errDeadOrGone if the identified entity is not Alive
    96  // or Dying, or a txn.Op that will fail if the condition no longer
    97  // holds.
    98  func (nsLife_) notDeadOp(entities mongo.Collection, docID string) (txn.Op, error) {
    99  	op, err := nsLife.checkOp(entities, docID, nsLife.notDead())
   100  	switch errors.Cause(err) {
   101  	case nil:
   102  	case errCheckFailed:
   103  		return txn.Op{}, errDeadOrGone
   104  	default:
   105  		return txn.Op{}, errors.Trace(err)
   106  	}
   107  	return op, nil
   108  }
   109  
   110  var errCheckFailed = errors.New("check failed")
   111  
   112  func (nsLife_) checkOp(entities mongo.Collection, docID string, check bson.D) (txn.Op, error) {
   113  	sel := append(bson.D{{"_id", docID}}, check...)
   114  	count, err := entities.Find(sel).Count()
   115  	if err != nil {
   116  		return txn.Op{}, errors.Trace(err)
   117  	} else if count == 0 {
   118  		return txn.Op{}, errCheckFailed
   119  	}
   120  	return txn.Op{
   121  		C:      entities.Name(),
   122  		Id:     docID,
   123  		Assert: check,
   124  	}, nil
   125  }
   126  
   127  func (nsLife_) read(entities mongo.Collection, docID string) (Life, error) {
   128  	var doc struct {
   129  		Life Life `bson:"life"`
   130  	}
   131  	err := entities.FindId(docID).One(&doc)
   132  	switch errors.Cause(err) {
   133  	case nil:
   134  	case mgo.ErrNotFound:
   135  		return Dead, errors.NotFoundf("entity")
   136  	default:
   137  		return Dead, errors.Trace(err)
   138  	}
   139  	return doc.Life, nil
   140  }
   141  
   142  // alive returns a selector that matches only documents whose life
   143  // field is set to Alive.
   144  func (nsLife_) alive() bson.D {
   145  	return bson.D{{"life", Alive}}
   146  }
   147  
   148  // dying returns a selector that matches only documents whose life
   149  // field is set to Dying.
   150  func (nsLife_) dying() bson.D {
   151  	return bson.D{{"life", Dying}}
   152  }
   153  
   154  // notDead returns a selector that matches only documents whose life
   155  // field is not set to Dead.
   156  func (nsLife_) notDead() bson.D {
   157  	return bson.D{{"life", bson.D{{"$ne", Dead}}}}
   158  }