github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils"
    12  	"gopkg.in/juju/names.v2"
    13  	"gopkg.in/mgo.v2"
    14  	"gopkg.in/mgo.v2/bson"
    15  	"gopkg.in/mgo.v2/txn"
    16  )
    17  
    18  const (
    19  	actionMarker = "_a_"
    20  )
    21  
    22  var (
    23  	actionLogger = loggo.GetLogger("juju.state.action")
    24  
    25  	// NewUUID wraps the utils.NewUUID() call, and exposes it as a var to
    26  	// facilitate patching.
    27  	NewUUID = func() (utils.UUID, error) { return utils.NewUUID() }
    28  )
    29  
    30  // ActionStatus represents the possible end states for an action.
    31  type ActionStatus string
    32  
    33  const (
    34  	// ActionFailed signifies that the action did not complete successfully.
    35  	ActionFailed ActionStatus = "failed"
    36  
    37  	// ActionCompleted indicates that the action ran to completion as intended.
    38  	ActionCompleted ActionStatus = "completed"
    39  
    40  	// ActionCancelled means that the Action was cancelled before being run.
    41  	ActionCancelled ActionStatus = "cancelled"
    42  
    43  	// ActionPending is the default status when an Action is first queued.
    44  	ActionPending ActionStatus = "pending"
    45  
    46  	// ActionRunning indicates that the Action is currently running.
    47  	ActionRunning ActionStatus = "running"
    48  )
    49  
    50  type actionNotificationDoc struct {
    51  	// DocId is the composite _id that can be matched by an
    52  	// idPrefixWatcher that is configured to watch for the
    53  	// ActionReceiver Name() which makes up the first part of this
    54  	// composite _id.
    55  	DocId string `bson:"_id"`
    56  
    57  	// ModelUUID is the model identifier.
    58  	ModelUUID string `bson:"model-uuid"`
    59  
    60  	// Receiver is the Name of the Unit or any other ActionReceiver for
    61  	// which this notification is queued.
    62  	Receiver string `bson:"receiver"`
    63  
    64  	// ActionID is the unique identifier for the Action this notification
    65  	// represents.
    66  	ActionID string `bson:"actionid"`
    67  }
    68  
    69  type actionDoc struct {
    70  	// DocId is the key for this document; it is a UUID.
    71  	DocId string `bson:"_id"`
    72  
    73  	// ModelUUID is the model identifier.
    74  	ModelUUID string `bson:"model-uuid"`
    75  
    76  	// Receiver is the Name of the Unit or any other ActionReceiver for
    77  	// which this Action is queued.
    78  	Receiver string `bson:"receiver"`
    79  
    80  	// Name identifies the action that should be run; it should
    81  	// match an action defined by the unit's charm.
    82  	Name string `bson:"name"`
    83  
    84  	// Parameters holds the action's parameters, if any; it should validate
    85  	// against the schema defined by the named action in the unit's charm.
    86  	Parameters map[string]interface{} `bson:"parameters"`
    87  
    88  	// Enqueued is the time the action was added.
    89  	Enqueued time.Time `bson:"enqueued"`
    90  
    91  	// Started reflects the time the action began running.
    92  	Started time.Time `bson:"started"`
    93  
    94  	// Completed reflects the time that the action was finished.
    95  	Completed time.Time `bson:"completed"`
    96  
    97  	// Status represents the end state of the Action; ActionFailed for an
    98  	// action that was removed prematurely, or that failed, and
    99  	// ActionCompleted for an action that successfully completed.
   100  	Status ActionStatus `bson:"status"`
   101  
   102  	// Message captures any error returned by the action.
   103  	Message string `bson:"message"`
   104  
   105  	// Results are the structured results from the action.
   106  	Results map[string]interface{} `bson:"results"`
   107  }
   108  
   109  // action represents an instruction to do some "action" and is expected
   110  // to match an action definition in a charm.
   111  type action struct {
   112  	st  *State
   113  	doc actionDoc
   114  }
   115  
   116  // Id returns the local id of the Action.
   117  func (a *action) Id() string {
   118  	return a.st.localID(a.doc.DocId)
   119  }
   120  
   121  // Receiver returns the Name of the ActionReceiver for which this action
   122  // is enqueued.  Usually this is a Unit Name().
   123  func (a *action) Receiver() string {
   124  	return a.doc.Receiver
   125  }
   126  
   127  // Name returns the name of the action, as defined in the charm.
   128  func (a *action) Name() string {
   129  	return a.doc.Name
   130  }
   131  
   132  // Parameters will contain a structure representing arguments or parameters to
   133  // an action, and is expected to be validated by the Unit using the Charm
   134  // definition of the Action.
   135  func (a *action) Parameters() map[string]interface{} {
   136  	return a.doc.Parameters
   137  }
   138  
   139  // Enqueued returns the time the action was added to state as a pending
   140  // Action.
   141  func (a *action) Enqueued() time.Time {
   142  	return a.doc.Enqueued
   143  }
   144  
   145  // Started returns the time that the Action execution began.
   146  func (a *action) Started() time.Time {
   147  	return a.doc.Started
   148  }
   149  
   150  // Completed returns the completion time of the Action.
   151  func (a *action) Completed() time.Time {
   152  	return a.doc.Completed
   153  }
   154  
   155  // Status returns the final state of the action.
   156  func (a *action) Status() ActionStatus {
   157  	return a.doc.Status
   158  }
   159  
   160  // Results returns the structured output of the action and any error.
   161  func (a *action) Results() (map[string]interface{}, string) {
   162  	return a.doc.Results, a.doc.Message
   163  }
   164  
   165  // Tag implements the Entity interface and returns a names.Tag that
   166  // is a names.ActionTag.
   167  func (a *action) Tag() names.Tag {
   168  	return a.ActionTag()
   169  }
   170  
   171  // ActionTag returns an ActionTag constructed from this action's
   172  // Prefix and Sequence.
   173  func (a *action) ActionTag() names.ActionTag {
   174  	return names.NewActionTag(a.Id())
   175  }
   176  
   177  // ActionResults is a data transfer object that holds the key Action
   178  // output and results information.
   179  type ActionResults struct {
   180  	Status  ActionStatus           `json:"status"`
   181  	Results map[string]interface{} `json:"results"`
   182  	Message string                 `json:"message"`
   183  }
   184  
   185  // Begin marks an action as running, and logs the time it was started.
   186  // It asserts that the action is currently pending.
   187  func (a *action) Begin() (Action, error) {
   188  	err := a.st.runTransaction([]txn.Op{
   189  		{
   190  			C:      actionsC,
   191  			Id:     a.doc.DocId,
   192  			Assert: bson.D{{"status", ActionPending}},
   193  			Update: bson.D{{"$set", bson.D{
   194  				{"status", ActionRunning},
   195  				{"started", a.st.NowToTheSecond()},
   196  			}}},
   197  		}})
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	return a.st.Action(a.Id())
   202  }
   203  
   204  // Finish removes action from the pending queue and captures the output
   205  // and end state of the action.
   206  func (a *action) Finish(results ActionResults) (Action, error) {
   207  	return a.removeAndLog(results.Status, results.Results, results.Message)
   208  }
   209  
   210  // removeAndLog takes the action off of the pending queue, and creates
   211  // an actionresult to capture the outcome of the action. It asserts that
   212  // the action is not already completed.
   213  func (a *action) removeAndLog(finalStatus ActionStatus, results map[string]interface{}, message string) (Action, error) {
   214  	err := a.st.runTransaction([]txn.Op{
   215  		{
   216  			C:  actionsC,
   217  			Id: a.doc.DocId,
   218  			Assert: bson.D{{"status", bson.D{
   219  				{"$nin", []interface{}{
   220  					ActionCompleted,
   221  					ActionCancelled,
   222  					ActionFailed,
   223  				}}}}},
   224  			Update: bson.D{{"$set", bson.D{
   225  				{"status", finalStatus},
   226  				{"message", message},
   227  				{"results", results},
   228  				{"completed", a.st.NowToTheSecond()},
   229  			}}},
   230  		}, {
   231  			C:      actionNotificationsC,
   232  			Id:     a.st.docID(ensureActionMarker(a.Receiver()) + a.Id()),
   233  			Remove: true,
   234  		}})
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	return a.st.Action(a.Id())
   239  }
   240  
   241  // newAction builds an Action for the given State and actionDoc.
   242  func newAction(st *State, adoc actionDoc) Action {
   243  	return &action{
   244  		st:  st,
   245  		doc: adoc,
   246  	}
   247  }
   248  
   249  // newActionDoc builds the actionDoc with the given name and parameters.
   250  func newActionDoc(st *State, receiverTag names.Tag, actionName string, parameters map[string]interface{}) (actionDoc, actionNotificationDoc, error) {
   251  	prefix := ensureActionMarker(receiverTag.Id())
   252  	actionId, err := NewUUID()
   253  	if err != nil {
   254  		return actionDoc{}, actionNotificationDoc{}, err
   255  	}
   256  	actionLogger.Debugf("newActionDoc name: '%s', receiver: '%s', actionId: '%s'", actionName, receiverTag, actionId)
   257  	modelUUID := st.ModelUUID()
   258  	return actionDoc{
   259  			DocId:      st.docID(actionId.String()),
   260  			ModelUUID:  modelUUID,
   261  			Receiver:   receiverTag.Id(),
   262  			Name:       actionName,
   263  			Parameters: parameters,
   264  			Enqueued:   st.NowToTheSecond(),
   265  			Status:     ActionPending,
   266  		}, actionNotificationDoc{
   267  			DocId:     st.docID(prefix + actionId.String()),
   268  			ModelUUID: modelUUID,
   269  			Receiver:  receiverTag.Id(),
   270  			ActionID:  actionId.String(),
   271  		}, nil
   272  }
   273  
   274  var ensureActionMarker = ensureSuffixFn(actionMarker)
   275  
   276  // Action returns an Action by Id, which is a UUID.
   277  func (st *State) Action(id string) (Action, error) {
   278  	actionLogger.Tracef("Action() %q", id)
   279  	actions, closer := st.getCollection(actionsC)
   280  	defer closer()
   281  
   282  	doc := actionDoc{}
   283  	err := actions.FindId(id).One(&doc)
   284  	if err == mgo.ErrNotFound {
   285  		return nil, errors.NotFoundf("action %q", id)
   286  	}
   287  	if err != nil {
   288  		return nil, errors.Annotatef(err, "cannot get action %q", id)
   289  	}
   290  	actionLogger.Tracef("Action() %q found %+v", id, doc)
   291  	return newAction(st, doc), nil
   292  }
   293  
   294  // AllActions returns all Actions.
   295  func (st *State) AllActions() ([]Action, error) {
   296  	actionLogger.Tracef("AllActions()")
   297  	actions, closer := st.getCollection(actionsC)
   298  	defer closer()
   299  
   300  	results := []Action{}
   301  	docs := []actionDoc{}
   302  	err := actions.Find(nil).All(&docs)
   303  	if err != nil {
   304  		return nil, errors.Annotatef(err, "cannot get all actions")
   305  	}
   306  	for _, doc := range docs {
   307  		results = append(results, newAction(st, doc))
   308  	}
   309  	return results, nil
   310  }
   311  
   312  // ActionByTag returns an Action given an ActionTag.
   313  func (st *State) ActionByTag(tag names.ActionTag) (Action, error) {
   314  	return st.Action(tag.Id())
   315  }
   316  
   317  // FindActionTagsByPrefix finds Actions with ids that share the supplied prefix, and
   318  // returns a list of corresponding ActionTags.
   319  func (st *State) FindActionTagsByPrefix(prefix string) []names.ActionTag {
   320  	actionLogger.Tracef("FindActionTagsByPrefix() %q", prefix)
   321  	var results []names.ActionTag
   322  	var doc struct {
   323  		Id string `bson:"_id"`
   324  	}
   325  
   326  	actions, closer := st.getCollection(actionsC)
   327  	defer closer()
   328  
   329  	iter := actions.Find(bson.D{{"_id", bson.D{{"$regex", "^" + st.docID(prefix)}}}}).Iter()
   330  	defer iter.Close()
   331  	for iter.Next(&doc) {
   332  		actionLogger.Tracef("FindActionTagsByPrefix() iter doc %+v", doc)
   333  		localID := st.localID(doc.Id)
   334  		if names.IsValidAction(localID) {
   335  			results = append(results, names.NewActionTag(localID))
   336  		}
   337  	}
   338  	actionLogger.Tracef("FindActionTagsByPrefix() %q found %+v", prefix, results)
   339  	return results
   340  }
   341  
   342  // FindActionsByName finds Actions with the given name.
   343  func (st *State) FindActionsByName(name string) ([]Action, error) {
   344  	var results []Action
   345  	var doc actionDoc
   346  
   347  	actions, closer := st.getCollection(actionsC)
   348  	defer closer()
   349  
   350  	iter := actions.Find(bson.D{{"name", name}}).Iter()
   351  	for iter.Next(&doc) {
   352  		results = append(results, newAction(st, doc))
   353  	}
   354  	return results, errors.Trace(iter.Close())
   355  }
   356  
   357  // EnqueueAction
   358  func (st *State) EnqueueAction(receiver names.Tag, actionName string, payload map[string]interface{}) (Action, error) {
   359  	if len(actionName) == 0 {
   360  		return nil, errors.New("action name required")
   361  	}
   362  
   363  	receiverCollectionName, receiverId, err := st.tagToCollectionAndId(receiver)
   364  	if err != nil {
   365  		return nil, errors.Trace(err)
   366  	}
   367  
   368  	doc, ndoc, err := newActionDoc(st, receiver, actionName, payload)
   369  	if err != nil {
   370  		return nil, errors.Trace(err)
   371  	}
   372  
   373  	ops := []txn.Op{{
   374  		C:      receiverCollectionName,
   375  		Id:     receiverId,
   376  		Assert: notDeadDoc,
   377  	}, {
   378  		C:      actionsC,
   379  		Id:     doc.DocId,
   380  		Assert: txn.DocMissing,
   381  		Insert: doc,
   382  	}, {
   383  		C:      actionNotificationsC,
   384  		Id:     ndoc.DocId,
   385  		Assert: txn.DocMissing,
   386  		Insert: ndoc,
   387  	}}
   388  
   389  	buildTxn := func(attempt int) ([]txn.Op, error) {
   390  		if notDead, err := isNotDead(st, receiverCollectionName, receiverId); err != nil {
   391  			return nil, err
   392  		} else if !notDead {
   393  			return nil, ErrDead
   394  		} else if attempt != 0 {
   395  			return nil, errors.Errorf("unexpected attempt number '%d'", attempt)
   396  		}
   397  		return ops, nil
   398  	}
   399  	if err = st.run(buildTxn); err == nil {
   400  		return newAction(st, doc), nil
   401  	}
   402  	return nil, err
   403  }
   404  
   405  // matchingActions finds actions that match ActionReceiver.
   406  func (st *State) matchingActions(ar ActionReceiver) ([]Action, error) {
   407  	return st.matchingActionsByReceiverId(ar.Tag().Id())
   408  }
   409  
   410  // matchingActionsByReceiverId finds actions that match ActionReceiver name.
   411  func (st *State) matchingActionsByReceiverId(id string) ([]Action, error) {
   412  	var doc actionDoc
   413  	var actions []Action
   414  
   415  	actionsCollection, closer := st.getCollection(actionsC)
   416  	defer closer()
   417  
   418  	iter := actionsCollection.Find(bson.D{{"receiver", id}}).Iter()
   419  	for iter.Next(&doc) {
   420  		actions = append(actions, newAction(st, doc))
   421  	}
   422  	return actions, errors.Trace(iter.Close())
   423  }
   424  
   425  // matchingActionsPending finds actions that match ActionReceiver and
   426  // that are pending.
   427  func (st *State) matchingActionsPending(ar ActionReceiver) ([]Action, error) {
   428  	completed := bson.D{{"status", ActionPending}}
   429  	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
   430  }
   431  
   432  // matchingActionsRunning finds actions that match ActionReceiver and
   433  // that are running.
   434  func (st *State) matchingActionsRunning(ar ActionReceiver) ([]Action, error) {
   435  	completed := bson.D{{"status", ActionRunning}}
   436  	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
   437  }
   438  
   439  // matchingActionsCompleted finds actions that match ActionReceiver and
   440  // that are complete.
   441  func (st *State) matchingActionsCompleted(ar ActionReceiver) ([]Action, error) {
   442  	completed := bson.D{{"$or", []bson.D{
   443  		{{"status", ActionCompleted}},
   444  		{{"status", ActionCancelled}},
   445  		{{"status", ActionFailed}},
   446  	}}}
   447  	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
   448  }
   449  
   450  // matchingActionsByReceiverAndStatus finds actionNotifications that
   451  // match ActionReceiver.
   452  func (st *State) matchingActionsByReceiverAndStatus(tag names.Tag, statusCondition bson.D) ([]Action, error) {
   453  	var doc actionDoc
   454  	var actions []Action
   455  
   456  	actionsCollection, closer := st.getCollection(actionsC)
   457  	defer closer()
   458  
   459  	sel := append(bson.D{{"receiver", tag.Id()}}, statusCondition...)
   460  	iter := actionsCollection.Find(sel).Iter()
   461  
   462  	for iter.Next(&doc) {
   463  		actions = append(actions, newAction(st, doc))
   464  	}
   465  	return actions, errors.Trace(iter.Close())
   466  }