github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"github.com/juju/mgo/v3/bson"
     9  	"github.com/juju/mgo/v3/txn"
    10  
    11  	"github.com/juju/juju/core/payloads"
    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() ([]payloads.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 unit.ShouldBeAssigned() && 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) ([]payloads.Result, error) {
    64  
    65  	var sel bson.D
    66  	var out func([]payloadDoc) []payloads.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) []payloads.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 payloads.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(payloads.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 payloads.ErrNotFound
   131  // is returned.
   132  func (up UnitPayloads) SetStatus(name, status string) error {
   133  	if err := payloads.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  	unit := change.Doc.UnitID
   171  	units, uCloser := db.GetCollection(unitsC)
   172  	defer uCloser()
   173  	unitOp, err := nsLife.notDeadOp(units, unit)
   174  	if errors.Cause(err) == errDeadOrGone {
   175  		return nil, errors.Errorf("unit %q no longer available", unit)
   176  	} else if err != nil {
   177  		return nil, errors.Trace(err)
   178  	}
   179  
   180  	payloads, pCloser := db.GetCollection(payloadsC)
   181  	defer pCloser()
   182  	payloadOp, err := nsPayloads.trackOp(payloads, change.Doc)
   183  	if err != nil {
   184  		return nil, errors.Trace(err)
   185  	}
   186  
   187  	return []txn.Op{unitOp, payloadOp}, nil
   188  }
   189  
   190  // payloadSetStatusChange updates a single payload status.
   191  type payloadSetStatusChange struct {
   192  	Unit   string
   193  	Name   string
   194  	Status string
   195  }
   196  
   197  // Prepare is part of the Change interface.
   198  func (change payloadSetStatusChange) Prepare(db Database) ([]txn.Op, error) {
   199  	docID := nsPayloads.docID(change.Unit, change.Name)
   200  	payloadColl, closer := db.GetCollection(payloadsC)
   201  	defer closer()
   202  
   203  	op, err := nsPayloads.setStatusOp(payloadColl, docID, change.Status)
   204  	if errors.Cause(err) == errAlreadyRemoved {
   205  		return nil, payloads.ErrNotFound
   206  	} else if err != nil {
   207  		return nil, errors.Trace(err)
   208  	}
   209  	return []txn.Op{op}, nil
   210  }
   211  
   212  // payloadUntrackChange removes a single unit payload.
   213  type payloadUntrackChange struct {
   214  	Unit string
   215  	Name string
   216  }
   217  
   218  // Prepare is part of the Change interface.
   219  func (change payloadUntrackChange) Prepare(db Database) ([]txn.Op, error) {
   220  	docID := nsPayloads.docID(change.Unit, change.Name)
   221  	payloads, closer := db.GetCollection(payloadsC)
   222  	defer closer()
   223  
   224  	op, err := nsPayloads.untrackOp(payloads, docID)
   225  	if errors.Cause(err) == errAlreadyRemoved {
   226  		return nil, ErrChangeComplete
   227  	} else if err != nil {
   228  		return nil, errors.Trace(err)
   229  	}
   230  	return []txn.Op{op}, nil
   231  }
   232  
   233  // payloadCleanupChange removes all unit payloads.
   234  type payloadCleanupChange struct {
   235  	Unit string
   236  }
   237  
   238  // Prepare is part of the Change interface.
   239  func (change payloadCleanupChange) Prepare(db Database) ([]txn.Op, error) {
   240  	payloads, closer := db.GetCollection(payloadsC)
   241  	defer closer()
   242  
   243  	sel := nsPayloads.forUnit(change.Unit)
   244  	fields := bson.D{{"_id", 1}}
   245  	var docs []struct {
   246  		DocID string `bson:"_id"`
   247  	}
   248  	err := payloads.Find(sel).Select(fields).All(&docs)
   249  	if err != nil {
   250  		return nil, errors.Trace(err)
   251  	} else if len(docs) == 0 {
   252  		return nil, ErrChangeComplete
   253  	}
   254  
   255  	ops := make([]txn.Op, 0, len(docs))
   256  	for _, doc := range docs {
   257  		op, err := nsPayloads.untrackOp(payloads, doc.DocID)
   258  		if errors.Cause(err) == errAlreadyRemoved {
   259  			continue
   260  		} else if err != nil {
   261  			return nil, errors.Trace(err)
   262  		}
   263  		ops = append(ops, op)
   264  	}
   265  	return ops, nil
   266  }