github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/payloads.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  	"github.com/juju/errors"
     8  	"gopkg.in/mgo.v2/bson"
     9  	"gopkg.in/mgo.v2/txn"
    10  
    11  	"github.com/juju/juju/payload"
    12  )
    13  
    14  // ModelPayloads returns a ModelPayloads for the state's model.
    15  func (st *State) ModelPayloads() (ModelPayloads, error) {
    16  	return ModelPayloads{
    17  		db: st.database,
    18  	}, nil
    19  }
    20  
    21  // ModelPayloads lets you read all unit payloads in a model.
    22  type ModelPayloads struct {
    23  	db Database
    24  }
    25  
    26  // ListAll builds the list of payload information that is registered in state.
    27  func (mp ModelPayloads) ListAll() ([]payload.FullPayloadInfo, error) {
    28  	coll, closer := mp.db.GetCollection(payloadsC)
    29  	defer closer()
    30  
    31  	var docs []payloadDoc
    32  	if err := coll.Find(nil).All(&docs); err != nil {
    33  		return nil, errors.Trace(err)
    34  	}
    35  	return nsPayloads.asPayloads(docs), nil
    36  }
    37  
    38  // UnitPayloads returns a UnitPayloads for the supplied unit.
    39  func (st *State) UnitPayloads(unit *Unit) (UnitPayloads, error) {
    40  	machineID, err := unit.AssignedMachineId()
    41  	if err != nil {
    42  		return UnitPayloads{}, errors.Trace(err)
    43  	}
    44  	return UnitPayloads{
    45  		db:      st.database,
    46  		unit:    unit.Name(),
    47  		machine: machineID,
    48  	}, nil
    49  }
    50  
    51  // UnitPayloads lets you CRUD payloads for a single unit.
    52  type UnitPayloads struct {
    53  	db      Database
    54  	unit    string
    55  	machine string
    56  }
    57  
    58  // List has two different modes of operation, because that's never a bad
    59  // idea. If you pass no args, it returns information about all payloads
    60  // tracked by the unit; if you pass names, it returns a slice of results
    61  // corresponding to names, in which any names not tracked have both the
    62  // NotFound field *and* an Error set.
    63  func (up UnitPayloads) List(names ...string) ([]payload.Result, error) {
    64  
    65  	var sel bson.D
    66  	var out func([]payloadDoc) []payload.Result
    67  	if len(names) == 0 {
    68  		sel = nsPayloads.forUnit(up.unit)
    69  		out = nsPayloads.asResults
    70  	} else {
    71  		sel = nsPayloads.forUnitWithNames(up.unit, names)
    72  		out = func(docs []payloadDoc) []payload.Result {
    73  			return nsPayloads.orderedResults(docs, names)
    74  		}
    75  	}
    76  
    77  	coll, closer := up.db.GetCollection(payloadsC)
    78  	defer closer()
    79  	var docs []payloadDoc
    80  	if err := coll.Find(sel).All(&docs); err != nil {
    81  		return nil, errors.Trace(err)
    82  	}
    83  	return out(docs), nil
    84  }
    85  
    86  // LookUp returns its first argument and no error.
    87  func (UnitPayloads) LookUp(name, rawID string) (string, error) {
    88  	// This method *is* used in the apiserver layer, both to extract
    89  	// the name from the payload and to implement the LookUp facade
    90  	// method which allows clients to ask the server what the first
    91  	// of two strings might be.
    92  	//
    93  	// The previous implementation would hit the db to as well, to
    94  	// exactly the same effect as implemented here. Would drop the
    95  	// whole useless slice, but don't want to bloat the diff.
    96  	return name, nil
    97  }
    98  
    99  // Track inserts the provided payload info in state. If the payload
   100  // is already in the DB then it is replaced.
   101  func (up UnitPayloads) Track(pl payload.Payload) error {
   102  
   103  	// XXX OMFG payload/context/register.go:83 launches bad data
   104  	// which flies on a majestic unvalidated arc right through the
   105  	// system until it lands here. This code should be:
   106  	//
   107  	//    if pl.Unit != up.unit {
   108  	//        return errors.NotValidf("unexpected Unit %q", pl.Unit)
   109  	//    }
   110  	//
   111  	// ...but is instead:
   112  	pl.Unit = up.unit
   113  
   114  	if err := pl.Validate(); err != nil {
   115  		return errors.Trace(err)
   116  	}
   117  
   118  	doc := nsPayloads.asDoc(payload.FullPayloadInfo{
   119  		Payload: pl,
   120  		Machine: up.machine,
   121  	})
   122  	change := payloadTrackChange{doc}
   123  	if err := Apply(up.db, change); err != nil {
   124  		return errors.Trace(err)
   125  	}
   126  	return nil
   127  }
   128  
   129  // SetStatus updates the raw status for the identified payload to the
   130  // provided value. If the payload is missing then payload.ErrNotFound
   131  // is returned.
   132  func (up UnitPayloads) SetStatus(name, status string) error {
   133  	if err := payload.ValidateState(status); err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  
   137  	change := payloadSetStatusChange{
   138  		Unit:   up.unit,
   139  		Name:   name,
   140  		Status: status,
   141  	}
   142  	if err := Apply(up.db, change); err != nil {
   143  		return errors.Trace(err)
   144  	}
   145  	return nil
   146  }
   147  
   148  // Untrack removes the identified payload from state. It does not
   149  // trigger the actual destruction of the payload. If the payload is
   150  // missing then this is a noop.
   151  func (up UnitPayloads) Untrack(name string) error {
   152  	logger.Tracef("untracking %q", name)
   153  	change := payloadUntrackChange{
   154  		Unit: up.unit,
   155  		Name: name,
   156  	}
   157  	if err := Apply(up.db, change); err != nil {
   158  		return errors.Trace(err)
   159  	}
   160  	return nil
   161  }
   162  
   163  // payloadTrackChange records a single unit payload.
   164  type payloadTrackChange struct {
   165  	Doc payloadDoc
   166  }
   167  
   168  // Prepare is part of the Change interface.
   169  func (change payloadTrackChange) Prepare(db Database) ([]txn.Op, error) {
   170  
   171  	unit := change.Doc.UnitID
   172  	units, closer := db.GetCollection(unitsC)
   173  	defer closer()
   174  	unitOp, err := nsLife.notDeadOp(units, unit)
   175  	if errors.Cause(err) == errDeadOrGone {
   176  		return nil, errors.Errorf("unit %q no longer available", unit)
   177  	} else if err != nil {
   178  		return nil, errors.Trace(err)
   179  	}
   180  
   181  	payloads, closer := db.GetCollection(payloadsC)
   182  	defer closer()
   183  	payloadOp, err := nsPayloads.trackOp(payloads, change.Doc)
   184  	if err != nil {
   185  		return nil, errors.Trace(err)
   186  	}
   187  
   188  	return []txn.Op{unitOp, payloadOp}, nil
   189  }
   190  
   191  // payloadSetStatusChange updates a single payload status.
   192  type payloadSetStatusChange struct {
   193  	Unit   string
   194  	Name   string
   195  	Status string
   196  }
   197  
   198  // Prepare is part of the Change interface.
   199  func (change payloadSetStatusChange) Prepare(db Database) ([]txn.Op, error) {
   200  	docID := nsPayloads.docID(change.Unit, change.Name)
   201  	payloads, closer := db.GetCollection(payloadsC)
   202  	defer closer()
   203  
   204  	op, err := nsPayloads.setStatusOp(payloads, docID, change.Status)
   205  	if errors.Cause(err) == errAlreadyRemoved {
   206  		return nil, payload.ErrNotFound
   207  	} else if err != nil {
   208  		return nil, errors.Trace(err)
   209  	}
   210  	return []txn.Op{op}, nil
   211  }
   212  
   213  // payloadUntrackChange removes a single unit payload.
   214  type payloadUntrackChange struct {
   215  	Unit string
   216  	Name string
   217  }
   218  
   219  // Prepare is part of the Change interface.
   220  func (change payloadUntrackChange) Prepare(db Database) ([]txn.Op, error) {
   221  	docID := nsPayloads.docID(change.Unit, change.Name)
   222  	payloads, closer := db.GetCollection(payloadsC)
   223  	defer closer()
   224  
   225  	op, err := nsPayloads.untrackOp(payloads, docID)
   226  	if errors.Cause(err) == errAlreadyRemoved {
   227  		return nil, ErrChangeComplete
   228  	} else if err != nil {
   229  		return nil, errors.Trace(err)
   230  	}
   231  	return []txn.Op{op}, nil
   232  }
   233  
   234  // payloadCleanupChange removes all unit payloads.
   235  type payloadCleanupChange struct {
   236  	Unit string
   237  }
   238  
   239  // Prepare is part of the Change interface.
   240  func (change payloadCleanupChange) Prepare(db Database) ([]txn.Op, error) {
   241  	payloads, closer := db.GetCollection(payloadsC)
   242  	defer closer()
   243  
   244  	sel := nsPayloads.forUnit(change.Unit)
   245  	fields := bson.D{{"_id", 1}}
   246  	var docs []struct {
   247  		DocID string `bson:"_id"`
   248  	}
   249  	err := payloads.Find(sel).Select(fields).All(&docs)
   250  	if err != nil {
   251  		return nil, errors.Trace(err)
   252  	} else if len(docs) == 0 {
   253  		return nil, ErrChangeComplete
   254  	}
   255  
   256  	ops := make([]txn.Op, 0, len(docs))
   257  	for _, doc := range docs {
   258  		op, err := nsPayloads.untrackOp(payloads, doc.DocID)
   259  		if errors.Cause(err) == errAlreadyRemoved {
   260  			continue
   261  		} else if err != nil {
   262  			return nil, errors.Trace(err)
   263  		}
   264  		ops = append(ops, op)
   265  	}
   266  	return ops, nil
   267  }