github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/action.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/juju/collections/set"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/mgo/v3"
    15  	"github.com/juju/mgo/v3/bson"
    16  	"github.com/juju/mgo/v3/txn"
    17  	"github.com/juju/names/v5"
    18  	jujutxn "github.com/juju/txn/v3"
    19  
    20  	stateerrors "github.com/juju/juju/state/errors"
    21  )
    22  
    23  const (
    24  	actionMarker = "_a_"
    25  )
    26  
    27  var actionLogger = loggo.GetLogger("juju.state.action")
    28  
    29  // ActionStatus represents the possible end states for an action.
    30  type ActionStatus string
    31  
    32  const (
    33  	// ActionError signifies that the action did not get run due to an error.
    34  	ActionError ActionStatus = "error"
    35  
    36  	// ActionFailed signifies that the action did not complete successfully.
    37  	ActionFailed ActionStatus = "failed"
    38  
    39  	// ActionCompleted indicates that the action ran to completion as intended.
    40  	ActionCompleted ActionStatus = "completed"
    41  
    42  	// ActionCancelled means that the Action was cancelled before being run.
    43  	ActionCancelled ActionStatus = "cancelled"
    44  
    45  	// ActionPending is the default status when an Action is first queued.
    46  	ActionPending ActionStatus = "pending"
    47  
    48  	// ActionRunning indicates that the Action is currently running.
    49  	ActionRunning ActionStatus = "running"
    50  
    51  	// ActionAborting indicates that the Action is running but should be
    52  	// aborted.
    53  	ActionAborting ActionStatus = "aborting"
    54  
    55  	// ActionAborted indicates the Action was aborted.
    56  	ActionAborted ActionStatus = "aborted"
    57  )
    58  
    59  var activeStatus = set.NewStrings(
    60  	string(ActionPending),
    61  	string(ActionRunning),
    62  	string(ActionAborting),
    63  )
    64  
    65  type actionNotificationDoc struct {
    66  	// DocId is the composite _id that can be matched by an
    67  	// idPrefixWatcher that is configured to watch for the
    68  	// ActionReceiver Name() which makes up the first part of this
    69  	// composite _id.
    70  	DocId string `bson:"_id"`
    71  
    72  	// ModelUUID is the model identifier.
    73  	ModelUUID string `bson:"model-uuid"`
    74  
    75  	// Receiver is the Name of the Unit or any other ActionReceiver for
    76  	// which this notification is queued.
    77  	Receiver string `bson:"receiver"`
    78  
    79  	// ActionID is the unique identifier for the Action this notification
    80  	// represents.
    81  	ActionID string `bson:"actionid"`
    82  
    83  	// Changed represents the time when the corresponding Action had a change
    84  	// worthy of notifying on.
    85  	// NOTE: changed should not be set on pending actions, see actionNotificationWatcher.
    86  	Changed time.Time `bson:"changed,omitempty"`
    87  }
    88  
    89  type actionDoc struct {
    90  	// DocId is the key for this document; it is a UUID.
    91  	DocId string `bson:"_id"`
    92  
    93  	// ModelUUID is the model identifier.
    94  	ModelUUID string `bson:"model-uuid"`
    95  
    96  	// Receiver is the Name of the Unit or any other ActionReceiver for
    97  	// which this Action is queued.
    98  	Receiver string `bson:"receiver"`
    99  
   100  	// Name identifies the action that should be run; it should
   101  	// match an action defined by the unit's charm.
   102  	Name string `bson:"name"`
   103  
   104  	// Parameters holds the action's parameters, if any; it should validate
   105  	// against the schema defined by the named action in the unit's charm.
   106  	Parameters map[string]interface{} `bson:"parameters"`
   107  
   108  	// Parallel is true if this action can run in parallel with others
   109  	// without requiring the mandatory acquisition of the machine lock.
   110  	Parallel bool `bson:"parallel,omitempty"`
   111  
   112  	// ExecutionGroup is used to group all actions which require the
   113  	// same machine lock, ie actions in the same group cannot run in
   114  	// in parallel with each other.
   115  	ExecutionGroup string `bson:"execution-group,omitempty"`
   116  
   117  	// Enqueued is the time the action was added.
   118  	Enqueued time.Time `bson:"enqueued"`
   119  
   120  	// Started reflects the time the action began running.
   121  	Started time.Time `bson:"started"`
   122  
   123  	// Completed reflects the time that the action was finished.
   124  	Completed time.Time `bson:"completed"`
   125  
   126  	// Operation is the parent operation of the action.
   127  	Operation string `bson:"operation"`
   128  
   129  	// Status represents the end state of the Action; ActionFailed for an
   130  	// action that was removed prematurely, or that failed, and
   131  	// ActionCompleted for an action that successfully completed.
   132  	Status ActionStatus `bson:"status"`
   133  
   134  	// Message captures any error returned by the action.
   135  	Message string `bson:"message"`
   136  
   137  	// Results are the structured results from the action.
   138  	Results map[string]interface{} `bson:"results"`
   139  
   140  	// Logs holds the progress messages logged by the action.
   141  	Logs []ActionMessage `bson:"messages"`
   142  }
   143  
   144  // ActionMessage represents a progress message logged by an action.
   145  type ActionMessage struct {
   146  	MessageValue   string    `bson:"message"`
   147  	TimestampValue time.Time `bson:"timestamp"`
   148  }
   149  
   150  // Timestamp returns the message timestamp.
   151  func (m ActionMessage) Timestamp() time.Time {
   152  	return m.TimestampValue
   153  }
   154  
   155  // Message returns the message string.
   156  func (m ActionMessage) Message() string {
   157  	return m.MessageValue
   158  }
   159  
   160  // action represents an instruction to do some "action" and is expected
   161  // to match an action definition in a charm.
   162  type action struct {
   163  	st  *State
   164  	doc actionDoc
   165  }
   166  
   167  // Refresh the contents of the action.
   168  func (a *action) Refresh() error {
   169  	actions, closer := a.st.db().GetCollection(actionsC)
   170  	defer closer()
   171  	id := a.Id()
   172  	doc := actionDoc{}
   173  	err := actions.FindId(id).One(&doc)
   174  	if err == mgo.ErrNotFound {
   175  		return errors.NotFoundf("action %q", id)
   176  	}
   177  	if err != nil {
   178  		return errors.Annotatef(err, "cannot get action %q", id)
   179  	}
   180  	a.doc = doc
   181  	return nil
   182  }
   183  
   184  // Id returns the local id of the Action.
   185  func (a *action) Id() string {
   186  	return a.st.localID(a.doc.DocId)
   187  }
   188  
   189  // Receiver returns the Name of the ActionReceiver for which this action
   190  // is enqueued.  Usually this is a Unit Name().
   191  func (a *action) Receiver() string {
   192  	return a.doc.Receiver
   193  }
   194  
   195  // Name returns the name of the action, as defined in the charm.
   196  func (a *action) Name() string {
   197  	return a.doc.Name
   198  }
   199  
   200  // Parameters will contain a structure representing arguments or parameters to
   201  // an action, and is expected to be validated by the Unit using the Charm
   202  // definition of the Action.
   203  func (a *action) Parameters() map[string]interface{} {
   204  	return a.doc.Parameters
   205  }
   206  
   207  // Parallel returns true if the action can run without
   208  // needed to acquire the machine lock.
   209  func (a *action) Parallel() bool {
   210  	return a.doc.Parallel
   211  }
   212  
   213  // ExecutionGroup is the group of actions which cannot
   214  // execute in parallel with each other.
   215  func (a *action) ExecutionGroup() string {
   216  	return a.doc.ExecutionGroup
   217  }
   218  
   219  // Enqueued returns the time the action was added to state as a pending
   220  // Action.
   221  func (a *action) Enqueued() time.Time {
   222  	return a.doc.Enqueued
   223  }
   224  
   225  // Started returns the time that the Action execution began.
   226  func (a *action) Started() time.Time {
   227  	return a.doc.Started
   228  }
   229  
   230  // Completed returns the completion time of the Action.
   231  func (a *action) Completed() time.Time {
   232  	return a.doc.Completed
   233  }
   234  
   235  // Status returns the final state of the action.
   236  func (a *action) Status() ActionStatus {
   237  	return a.doc.Status
   238  }
   239  
   240  // Results returns the structured output of the action and any error.
   241  func (a *action) Results() (map[string]interface{}, string) {
   242  	return a.doc.Results, a.doc.Message
   243  }
   244  
   245  // Tag implements the Entity interface and returns a names.Tag that
   246  // is a names.ActionTag.
   247  func (a *action) Tag() names.Tag {
   248  	return a.ActionTag()
   249  }
   250  
   251  // ActionTag returns an ActionTag constructed from this action's
   252  // Prefix and Sequence.
   253  func (a *action) ActionTag() names.ActionTag {
   254  	return names.NewActionTag(a.Id())
   255  }
   256  
   257  // Model returns the model associated with the action
   258  func (a *action) Model() (*Model, error) {
   259  	return a.st.Model()
   260  }
   261  
   262  // ActionResults is a data transfer object that holds the key Action
   263  // output and results information.
   264  type ActionResults struct {
   265  	Status  ActionStatus           `json:"status"`
   266  	Results map[string]interface{} `json:"results"`
   267  	Message string                 `json:"message"`
   268  }
   269  
   270  // Begin marks an action as running, and logs the time it was started.
   271  // It asserts that the action is currently pending.
   272  func (a *action) Begin() (Action, error) {
   273  	m, err := a.Model()
   274  	if err != nil {
   275  		return nil, errors.Trace(err)
   276  	}
   277  	parentOperation, err := m.Operation(a.doc.Operation)
   278  	if err != nil && !errors.IsNotFound(err) {
   279  		return nil, errors.Trace(err)
   280  	}
   281  	startedTime := a.st.nowToTheSecond()
   282  	buildTxn := func(attempt int) ([]txn.Op, error) {
   283  		// If this is the first action to be marked as running
   284  		// for the parent operation, the operation itself is
   285  		// marked as running also.
   286  		var updateOperationOp *txn.Op
   287  		var err error
   288  		if parentOperation != nil {
   289  			if attempt > 0 {
   290  				err = parentOperation.Refresh()
   291  				if err != nil && !errors.IsNotFound(err) {
   292  					return nil, errors.Trace(err)
   293  				}
   294  			}
   295  			parentOpDetails, ok := parentOperation.(*operation)
   296  			if !ok {
   297  				return nil, errors.Errorf("parentOperation must be of type operation")
   298  			}
   299  			if err == nil && parentOpDetails.doc.Status == ActionPending {
   300  				updateOperationOp = &txn.Op{
   301  					C:      operationsC,
   302  					Id:     a.st.docID(parentOperation.Id()),
   303  					Assert: bson.D{{"status", ActionPending}},
   304  					Update: bson.D{{"$set", bson.D{
   305  						{"status", ActionRunning},
   306  						{"started", startedTime},
   307  					}}},
   308  				}
   309  			}
   310  		}
   311  		ops := []txn.Op{
   312  			{
   313  				C:      actionsC,
   314  				Id:     a.doc.DocId,
   315  				Assert: bson.D{{"status", ActionPending}},
   316  				Update: bson.D{{"$set", bson.D{
   317  					{"status", ActionRunning},
   318  					{"started", startedTime},
   319  				}}},
   320  			}}
   321  		if updateOperationOp != nil {
   322  			ops = append(ops, *updateOperationOp)
   323  		}
   324  		return ops, nil
   325  	}
   326  	if err = m.st.db().Run(buildTxn); err != nil {
   327  		return nil, errors.Trace(err)
   328  	}
   329  	return m.Action(a.Id())
   330  }
   331  
   332  // Finish removes action from the pending queue and captures the output
   333  // and end state of the action.
   334  func (a *action) Finish(results ActionResults) (Action, error) {
   335  	return a.removeAndLog(results.Status, results.Results, results.Message)
   336  }
   337  
   338  // Cancel or Abort the action.
   339  func (a *action) Cancel() (Action, error) {
   340  	m, err := a.Model()
   341  	if err != nil {
   342  		return nil, errors.Trace(err)
   343  	}
   344  
   345  	parentOperation, err := m.Operation(a.doc.Operation)
   346  	if err != nil && !errors.IsNotFound(err) {
   347  		return nil, errors.Trace(err)
   348  	}
   349  
   350  	cancelTime := a.st.nowToTheSecond()
   351  	removeAndLog := a.removeAndLogBuildTxn(ActionCancelled, nil, "action cancelled via the API",
   352  		m, parentOperation, cancelTime)
   353  	buildTxn := func(attempt int) ([]txn.Op, error) {
   354  		err := a.Refresh()
   355  		if err != nil {
   356  			return nil, errors.Trace(err)
   357  		}
   358  		switch a.Status() {
   359  		case ActionRunning:
   360  			ops := []txn.Op{
   361  				{
   362  					C:      actionsC,
   363  					Id:     a.doc.DocId,
   364  					Assert: bson.D{{"status", ActionRunning}},
   365  					Update: bson.D{{"$set", bson.D{
   366  						{"status", ActionAborting},
   367  					}}},
   368  				}, {
   369  					C:  actionNotificationsC,
   370  					Id: m.st.docID(ensureActionMarker(a.Receiver()) + a.Id()),
   371  					Update: bson.D{{"$set", bson.D{
   372  						{"changed", cancelTime},
   373  					}}},
   374  				},
   375  			}
   376  			return ops, nil
   377  		case ActionPending:
   378  			return removeAndLog(attempt)
   379  		default:
   380  			// Already done.
   381  			return nil, nil
   382  		}
   383  	}
   384  	if err = m.st.db().Run(buildTxn); err != nil {
   385  		return nil, errors.Trace(err)
   386  	}
   387  	return m.Action(a.Id())
   388  }
   389  
   390  // removeAndLog takes the action off of the pending queue, and creates
   391  // an actionresult to capture the outcome of the action. It asserts that
   392  // the action is not already completed.
   393  func (a *action) removeAndLog(finalStatus ActionStatus, results map[string]interface{}, message string) (Action, error) {
   394  	m, err := a.Model()
   395  	if err != nil {
   396  		return nil, errors.Trace(err)
   397  	}
   398  
   399  	parentOperation, err := m.Operation(a.doc.Operation)
   400  	if err != nil && !errors.IsNotFound(err) {
   401  		return nil, errors.Trace(err)
   402  	}
   403  
   404  	completedTime := a.st.nowToTheSecond()
   405  	buildTxn := a.removeAndLogBuildTxn(finalStatus, results, message, m, parentOperation, completedTime)
   406  	if err = m.st.db().Run(buildTxn); err != nil {
   407  		return nil, errors.Trace(err)
   408  	}
   409  	return m.Action(a.Id())
   410  }
   411  
   412  // removeAndLogBuildTxn is shared by Cancel and removeAndLog to correctly finalise an action and it's parent op.
   413  func (a *action) removeAndLogBuildTxn(finalStatus ActionStatus, results map[string]interface{}, message string,
   414  	m *Model, parentOperation Operation, completedTime time.Time) jujutxn.TransactionSource {
   415  	return func(attempt int) ([]txn.Op, error) {
   416  		// If the action is already finished, return an error.
   417  		if a.Status() == finalStatus {
   418  			return nil, errors.NewAlreadyExists(nil, fmt.Sprintf("action %v already has status %q", a.Id(), finalStatus))
   419  		}
   420  		if attempt > 0 {
   421  			err := a.Refresh()
   422  			if err != nil {
   423  				return nil, errors.Trace(err)
   424  			}
   425  			if !activeStatus.Contains(string(a.Status())) {
   426  				return nil, errors.NewAlreadyExists(nil, fmt.Sprintf("action %v is already finished with status %q", a.Id(), finalStatus))
   427  			}
   428  		}
   429  		assertNotComplete := bson.D{{"status", bson.D{
   430  			{"$nin", []interface{}{
   431  				ActionCompleted,
   432  				ActionCancelled,
   433  				ActionFailed,
   434  				ActionAborted,
   435  				ActionError,
   436  			}}}}}
   437  		// If this is the last action to be marked as completed
   438  		// for the parent operation, the operation itself is also
   439  		// marked as complete.
   440  		var updateOperationOp *txn.Op
   441  		var err error
   442  		if parentOperation != nil {
   443  			parentOpDetails, ok := parentOperation.(*operation)
   444  			if !ok {
   445  				return nil, errors.Errorf("parentOperation must be of type operation")
   446  			}
   447  			if attempt > 0 {
   448  				err = parentOperation.Refresh()
   449  				if err != nil && !errors.IsNotFound(err) {
   450  					return nil, errors.Trace(err)
   451  				}
   452  			}
   453  			tasks := parentOpDetails.taskStatus
   454  			statusStats := set.NewStrings(string(finalStatus))
   455  			var numComplete int
   456  			for _, status := range tasks {
   457  				statusStats.Add(string(status))
   458  				if !activeStatus.Contains(string(status)) {
   459  					numComplete++
   460  				}
   461  			}
   462  			spawnedTaskCount := parentOpDetails.doc.SpawnedTaskCount
   463  			if numComplete == spawnedTaskCount-1 {
   464  				// Set the operation status based on the individual
   465  				// task status values. eg if any task is failed,
   466  				// the entire operation is considered failed.
   467  				finalOperationStatus := finalStatus
   468  				for _, s := range statusCompletedOrder {
   469  					if statusStats.Contains(string(s)) {
   470  						finalOperationStatus = s
   471  						break
   472  					}
   473  				}
   474  				if finalOperationStatus == ActionCompleted && parentOpDetails.Fail() != "" {
   475  					// If an action fails enqueuing, there may not be a doc
   476  					// to reference. It will only be noted in the operation
   477  					// fail string.
   478  					finalOperationStatus = ActionError
   479  				}
   480  				updateOperationOp = &txn.Op{
   481  					C:  operationsC,
   482  					Id: a.st.docID(parentOperation.Id()),
   483  					Assert: append(assertNotComplete,
   484  						bson.DocElem{"complete-task-count", bson.D{{"$eq", spawnedTaskCount - 1}}}),
   485  					Update: bson.D{{"$set", bson.D{
   486  						{"status", finalOperationStatus},
   487  						{"completed", completedTime},
   488  						{"complete-task-count", spawnedTaskCount},
   489  					}}},
   490  				}
   491  			} else {
   492  				updateOperationOp = &txn.Op{
   493  					C:  operationsC,
   494  					Id: a.st.docID(parentOperation.Id()),
   495  					Assert: append(assertNotComplete,
   496  						bson.DocElem{"complete-task-count",
   497  							bson.D{{"$lt", spawnedTaskCount - 1}}}),
   498  					Update: bson.D{{"$inc", bson.D{
   499  						{"complete-task-count", 1},
   500  					}}},
   501  				}
   502  			}
   503  		}
   504  		ops := []txn.Op{
   505  			{
   506  				C:      actionsC,
   507  				Id:     a.doc.DocId,
   508  				Assert: assertNotComplete,
   509  				Update: bson.D{{"$set", bson.D{
   510  					{"status", finalStatus},
   511  					{"message", message},
   512  					{"results", results},
   513  					{"completed", completedTime},
   514  				}}},
   515  			}, {
   516  				C:      actionNotificationsC,
   517  				Id:     m.st.docID(ensureActionMarker(a.Receiver()) + a.Id()),
   518  				Remove: true,
   519  			}}
   520  		if updateOperationOp != nil {
   521  			ops = append(ops, *updateOperationOp)
   522  		}
   523  		return ops, nil
   524  	}
   525  }
   526  
   527  // Messages returns the action's progress messages.
   528  func (a *action) Messages() []ActionMessage {
   529  	// Timestamps are not decoded as UTC, so we need to convert :-(
   530  	result := make([]ActionMessage, len(a.doc.Logs))
   531  	for i, m := range a.doc.Logs {
   532  		result[i] = ActionMessage{
   533  			MessageValue:   m.MessageValue,
   534  			TimestampValue: m.TimestampValue.UTC(),
   535  		}
   536  	}
   537  	return result
   538  }
   539  
   540  // Log adds message to the action's progress message array.
   541  func (a *action) Log(message string) error {
   542  	// Just to ensure we do not allow bad actions to fill up disk.
   543  	// 1000 messages should be enough for anyone.
   544  	if len(a.doc.Logs) > 1000 {
   545  		logger.Warningf("exceeded 1000 log messages, action may be stuck")
   546  		return nil
   547  	}
   548  	m, err := a.st.Model()
   549  	if err != nil {
   550  		return errors.Trace(err)
   551  	}
   552  	buildTxn := func(attempt int) ([]txn.Op, error) {
   553  		if attempt > 0 {
   554  			anAction, err := m.Action(a.Id())
   555  			if err != nil {
   556  				return nil, errors.Trace(err)
   557  			}
   558  			a = anAction.(*action)
   559  		}
   560  		if s := a.Status(); s != ActionRunning && s != ActionAborting {
   561  			return nil, errors.Errorf("cannot log message to task %q with status %v", a.Id(), s)
   562  		}
   563  		ops := []txn.Op{
   564  			{
   565  				C:  actionsC,
   566  				Id: a.doc.DocId,
   567  				Assert: bson.D{{"$or", []bson.D{
   568  					{{"status", ActionRunning}},
   569  					{{"status", ActionAborting}},
   570  				}}},
   571  				Update: bson.D{{"$push", bson.D{
   572  					{"messages", ActionMessage{MessageValue: message, TimestampValue: a.st.nowToTheSecond().UTC()}},
   573  				}}},
   574  			}}
   575  		return ops, nil
   576  	}
   577  	err = a.st.db().Run(buildTxn)
   578  	return errors.Trace(err)
   579  }
   580  
   581  // newAction builds an Action for the given State and actionDoc.
   582  func newAction(st *State, adoc actionDoc) Action {
   583  	return &action{
   584  		st:  st,
   585  		doc: adoc,
   586  	}
   587  }
   588  
   589  // newActionDoc builds the actionDoc with the given name and parameters.
   590  func newActionDoc(mb modelBackend, operationID string, receiverTag names.Tag,
   591  	actionName string, parameters map[string]interface{}, parallel bool, executionGroup string) (actionDoc, actionNotificationDoc, error) {
   592  	prefix := ensureActionMarker(receiverTag.Id())
   593  	id, err := sequenceWithMin(mb, "task", 1)
   594  	if err != nil {
   595  		return actionDoc{}, actionNotificationDoc{}, err
   596  	}
   597  	actionId := strconv.Itoa(id)
   598  	actionLogger.Debugf("newActionDoc name: '%s', receiver: '%s', actionId: '%s'", actionName, receiverTag, actionId)
   599  	modelUUID := mb.ModelUUID()
   600  	return actionDoc{
   601  			DocId:          mb.docID(actionId),
   602  			ModelUUID:      modelUUID,
   603  			Receiver:       receiverTag.Id(),
   604  			Name:           actionName,
   605  			Parameters:     parameters,
   606  			Parallel:       parallel,
   607  			ExecutionGroup: executionGroup,
   608  			Enqueued:       mb.nowToTheSecond(),
   609  			Operation:      operationID,
   610  			Status:         ActionPending,
   611  		}, actionNotificationDoc{
   612  			DocId:     mb.docID(prefix + actionId),
   613  			ModelUUID: modelUUID,
   614  			Receiver:  receiverTag.Id(),
   615  			ActionID:  actionId,
   616  		}, nil
   617  }
   618  
   619  var ensureActionMarker = ensureSuffixFn(actionMarker)
   620  
   621  // Action returns an Action by Id.
   622  func (m *Model) Action(id string) (Action, error) {
   623  	actionLogger.Tracef("Action() %q", id)
   624  	st := m.st
   625  	actions, closer := st.db().GetCollection(actionsC)
   626  	defer closer()
   627  
   628  	doc := actionDoc{}
   629  	err := actions.FindId(id).One(&doc)
   630  	if err == mgo.ErrNotFound {
   631  		return nil, errors.NotFoundf("action %q", id)
   632  	}
   633  	if err != nil {
   634  		return nil, errors.Annotatef(err, "cannot get action %q", id)
   635  	}
   636  	actionLogger.Tracef("Action() %q found %+v", id, doc)
   637  	return newAction(st, doc), nil
   638  }
   639  
   640  // AllActions returns all Actions.
   641  func (m *Model) AllActions() ([]Action, error) {
   642  	actionLogger.Tracef("AllActions()")
   643  	actions, closer := m.st.db().GetCollection(actionsC)
   644  	defer closer()
   645  
   646  	results := []Action{}
   647  	docs := []actionDoc{}
   648  	err := actions.Find(nil).All(&docs)
   649  	if err != nil {
   650  		return nil, errors.Annotatef(err, "cannot get all actions")
   651  	}
   652  	for _, doc := range docs {
   653  		results = append(results, newAction(m.st, doc))
   654  	}
   655  	return results, nil
   656  }
   657  
   658  // ActionByTag returns an Action given an ActionTag.
   659  func (m *Model) ActionByTag(tag names.ActionTag) (Action, error) {
   660  	return m.Action(tag.Id())
   661  }
   662  
   663  // FindActionsByName finds Actions with the given name.
   664  func (m *Model) FindActionsByName(name string) ([]Action, error) {
   665  	var results []Action
   666  	var doc actionDoc
   667  
   668  	actions, closer := m.st.db().GetCollection(actionsC)
   669  	defer closer()
   670  
   671  	iter := actions.Find(bson.D{{"name", name}}).Iter()
   672  	for iter.Next(&doc) {
   673  		results = append(results, newAction(m.st, doc))
   674  	}
   675  	return results, errors.Trace(iter.Close())
   676  }
   677  
   678  // EnqueueAction caches the action doc to the database.
   679  func (m *Model) EnqueueAction(operationID string, receiver names.Tag,
   680  	actionName string, payload map[string]interface{}, parallel bool, executionGroup string, actionError error) (Action, error) {
   681  	if len(actionName) == 0 {
   682  		return nil, errors.New("action name required")
   683  	}
   684  
   685  	receiverCollectionName, receiverId, err := m.st.tagToCollectionAndId(receiver)
   686  	if err != nil {
   687  		return nil, errors.Trace(err)
   688  	}
   689  	doc, ndoc, err := newActionDoc(m.st, operationID, receiver, actionName, payload, parallel, executionGroup)
   690  	if err != nil {
   691  		return nil, errors.Trace(err)
   692  	}
   693  
   694  	if actionError != nil {
   695  		doc.Status = ActionError
   696  		doc.Message = actionError.Error()
   697  	}
   698  	ops := []txn.Op{{
   699  		C:      receiverCollectionName,
   700  		Id:     receiverId,
   701  		Assert: notDeadDoc,
   702  	}, {
   703  		C:      operationsC,
   704  		Id:     m.st.docID(operationID),
   705  		Assert: txn.DocExists,
   706  	}, {
   707  		C:      actionsC,
   708  		Id:     doc.DocId,
   709  		Assert: txn.DocMissing,
   710  		Insert: doc,
   711  	}}
   712  	if actionError == nil {
   713  		ops = append(ops, txn.Op{
   714  			C:      actionNotificationsC,
   715  			Id:     ndoc.DocId,
   716  			Assert: txn.DocMissing,
   717  			Insert: ndoc,
   718  		})
   719  	}
   720  
   721  	buildTxn := func(attempt int) ([]txn.Op, error) {
   722  		if notDead, err := isNotDead(m.st, receiverCollectionName, receiverId); err != nil {
   723  			return nil, err
   724  		} else if !notDead {
   725  			return nil, stateerrors.ErrDead
   726  		} else if attempt != 0 {
   727  			_, err := m.Operation(operationID)
   728  			if err != nil {
   729  				return nil, errors.Trace(err)
   730  			}
   731  			return nil, errors.Errorf("unexpected attempt number '%d'", attempt)
   732  		}
   733  		return ops, nil
   734  	}
   735  	if err = m.st.db().Run(buildTxn); err == nil {
   736  		return newAction(m.st, doc), nil
   737  	}
   738  	return nil, err
   739  }
   740  
   741  // AddAction adds a new Action of type name and using arguments payload to
   742  // the receiver, and returns its ID.
   743  func (m *Model) AddAction(receiver ActionReceiver, operationID, name string, payload map[string]interface{}, parallel *bool, executionGroup *string) (Action, error) {
   744  	payload, runParallel, runExecutionGroup, err := receiver.PrepareActionPayload(name, payload, parallel, executionGroup)
   745  	if err != nil {
   746  		_, err2 := m.EnqueueAction(operationID, receiver.Tag(), name, payload, runParallel, runExecutionGroup, err)
   747  		if err2 != nil {
   748  			err = err2
   749  		}
   750  		return nil, errors.Trace(err)
   751  	}
   752  	return m.EnqueueAction(operationID, receiver.Tag(), name, payload, runParallel, runExecutionGroup, nil)
   753  }
   754  
   755  // matchingActions finds actions that match ActionReceiver.
   756  func (st *State) matchingActions(ar ActionReceiver) ([]Action, error) {
   757  	return st.matchingActionsByReceiverId(ar.Tag().Id())
   758  }
   759  
   760  // matchingActionsByReceiverId finds actions that match ActionReceiver name.
   761  func (st *State) matchingActionsByReceiverId(id string) ([]Action, error) {
   762  	var doc actionDoc
   763  	var actions []Action
   764  
   765  	actionsCollection, closer := st.db().GetCollection(actionsC)
   766  	defer closer()
   767  
   768  	iter := actionsCollection.Find(bson.D{{"receiver", id}}).Iter()
   769  	for iter.Next(&doc) {
   770  		actions = append(actions, newAction(st, doc))
   771  	}
   772  	return actions, errors.Trace(iter.Close())
   773  }
   774  
   775  // matchingActionsPending finds actions that match ActionReceiver and
   776  // that are pending.
   777  func (st *State) matchingActionsPending(ar ActionReceiver) ([]Action, error) {
   778  	pending := bson.D{{"status", ActionPending}}
   779  	return st.matchingActionsByReceiverAndStatus(ar.Tag(), pending)
   780  }
   781  
   782  // matchingActionsRunning finds actions that match ActionReceiver and
   783  // that are running.
   784  func (st *State) matchingActionsRunning(ar ActionReceiver) ([]Action, error) {
   785  	running := bson.D{{"$or", []bson.D{
   786  		{{"status", ActionRunning}},
   787  		{{"status", ActionAborting}},
   788  	}}}
   789  	return st.matchingActionsByReceiverAndStatus(ar.Tag(), running)
   790  }
   791  
   792  // matchingActionsCompleted finds actions that match ActionReceiver and
   793  // that are complete.
   794  func (st *State) matchingActionsCompleted(ar ActionReceiver) ([]Action, error) {
   795  	completed := bson.D{{"$or", []bson.D{
   796  		{{"status", ActionCompleted}},
   797  		{{"status", ActionCancelled}},
   798  		{{"status", ActionFailed}},
   799  		{{"status", ActionAborted}},
   800  	}}}
   801  	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
   802  }
   803  
   804  // matchingActionsByReceiverAndStatus finds actionNotifications that
   805  // match ActionReceiver.
   806  func (st *State) matchingActionsByReceiverAndStatus(tag names.Tag, statusCondition bson.D) ([]Action, error) {
   807  	var doc actionDoc
   808  	var actions []Action
   809  
   810  	actionsCollection, closer := st.db().GetCollection(actionsC)
   811  	defer closer()
   812  
   813  	sel := append(bson.D{{"receiver", tag.Id()}}, statusCondition...)
   814  	iter := actionsCollection.Find(sel).Iter()
   815  
   816  	for iter.Next(&doc) {
   817  		actions = append(actions, newAction(st, doc))
   818  	}
   819  	return actions, errors.Trace(iter.Close())
   820  }
   821  
   822  // PruneOperations removes operation entries and their sub-tasks until
   823  // only logs newer than <maxLogTime> remain and also ensures
   824  // that the actions collection is smaller than <maxLogsMB> after the deletion.
   825  func PruneOperations(stop <-chan struct{}, st *State, maxHistoryTime time.Duration, maxHistoryMB int) error {
   826  	// There may be older actions without parent operations so try those first.
   827  	hasNoOperation := bson.D{{"$or", []bson.D{
   828  		{{"operation", ""}},
   829  		{{"operation", bson.D{{"$exists", false}}}},
   830  	}}}
   831  	coll, closer := st.db().GetRawCollection(actionsC)
   832  	defer closer()
   833  	err := pruneCollection(stop, st, maxHistoryTime, maxHistoryMB, coll, "completed", hasNoOperation, GoTime)
   834  	if err != nil {
   835  		return errors.Trace(err)
   836  	}
   837  	// First calculate the average ratio of tasks to operations. Since deletion is
   838  	// done at the operation level, and any associated tasks are then deleted, but
   839  	// the actions collection is where the disk space goes, we approximate the
   840  	// number of operations to delete to achieve a given size deduction based on
   841  	// the average ratio of number of operations to tasks.
   842  	operationsColl, closer := st.db().GetRawCollection(operationsC)
   843  	defer closer()
   844  	operationsCount, err := operationsColl.Count()
   845  	if err != nil {
   846  		return errors.Annotate(err, "retrieving operations collection count")
   847  	}
   848  	actionsColl, closer := st.db().GetRawCollection(actionsC)
   849  	defer closer()
   850  	actionsCount, err := actionsColl.Count()
   851  	if err != nil {
   852  		return errors.Annotate(err, "retrieving actions collection count")
   853  	}
   854  	sizeFactor := float64(actionsCount) / float64(operationsCount)
   855  
   856  	err = pruneCollectionAndChildren(stop, st, maxHistoryTime, maxHistoryMB, operationsColl, actionsColl, "completed", "operation", nil, sizeFactor, GoTime)
   857  	return errors.Trace(err)
   858  }