github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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/names" 12 "github.com/juju/utils" 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", 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", 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: 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 // ActionByTag returns an Action given an ActionTag. 295 func (st *State) ActionByTag(tag names.ActionTag) (Action, error) { 296 return st.Action(tag.Id()) 297 } 298 299 // FindActionTagsByPrefix finds Actions with ids that share the supplied prefix, and 300 // returns a list of corresponding ActionTags. 301 func (st *State) FindActionTagsByPrefix(prefix string) []names.ActionTag { 302 actionLogger.Tracef("FindActionTagsByPrefix() %q", prefix) 303 var results []names.ActionTag 304 var doc struct { 305 Id string `bson:"_id"` 306 } 307 308 actions, closer := st.getCollection(actionsC) 309 defer closer() 310 311 iter := actions.Find(bson.D{{"_id", bson.D{{"$regex", "^" + st.docID(prefix)}}}}).Iter() 312 defer iter.Close() 313 for iter.Next(&doc) { 314 actionLogger.Tracef("FindActionTagsByPrefix() iter doc %+v", doc) 315 localID := st.localID(doc.Id) 316 if names.IsValidAction(localID) { 317 results = append(results, names.NewActionTag(localID)) 318 } 319 } 320 actionLogger.Tracef("FindActionTagsByPrefix() %q found %+v", prefix, results) 321 return results 322 } 323 324 // FindActionsByName finds Actions with the given name. 325 func (st *State) FindActionsByName(name string) ([]Action, error) { 326 var results []Action 327 var doc actionDoc 328 329 actions, closer := st.getCollection(actionsC) 330 defer closer() 331 332 iter := actions.Find(bson.D{{"name", name}}).Iter() 333 for iter.Next(&doc) { 334 results = append(results, newAction(st, doc)) 335 } 336 return results, errors.Trace(iter.Close()) 337 } 338 339 // EnqueueAction 340 func (st *State) EnqueueAction(receiver names.Tag, actionName string, payload map[string]interface{}) (Action, error) { 341 if len(actionName) == 0 { 342 return nil, errors.New("action name required") 343 } 344 345 receiverCollectionName, receiverId, err := st.tagToCollectionAndId(receiver) 346 if err != nil { 347 return nil, errors.Trace(err) 348 } 349 350 doc, ndoc, err := newActionDoc(st, receiver, actionName, payload) 351 if err != nil { 352 return nil, errors.Trace(err) 353 } 354 355 ops := []txn.Op{{ 356 C: receiverCollectionName, 357 Id: receiverId, 358 Assert: notDeadDoc, 359 }, { 360 C: actionsC, 361 Id: doc.DocId, 362 Assert: txn.DocMissing, 363 Insert: doc, 364 }, { 365 C: actionNotificationsC, 366 Id: ndoc.DocId, 367 Assert: txn.DocMissing, 368 Insert: ndoc, 369 }} 370 371 buildTxn := func(attempt int) ([]txn.Op, error) { 372 if notDead, err := isNotDead(st, receiverCollectionName, receiverId); err != nil { 373 return nil, err 374 } else if !notDead { 375 return nil, ErrDead 376 } else if attempt != 0 { 377 return nil, errors.Errorf("unexpected attempt number '%d'", attempt) 378 } 379 return ops, nil 380 } 381 if err = st.run(buildTxn); err == nil { 382 return newAction(st, doc), nil 383 } 384 return nil, err 385 } 386 387 // matchingActions finds actions that match ActionReceiver. 388 func (st *State) matchingActions(ar ActionReceiver) ([]Action, error) { 389 return st.matchingActionsByReceiverId(ar.Tag().Id()) 390 } 391 392 // matchingActionsByReceiverId finds actions that match ActionReceiver name. 393 func (st *State) matchingActionsByReceiverId(id string) ([]Action, error) { 394 var doc actionDoc 395 var actions []Action 396 397 actionsCollection, closer := st.getCollection(actionsC) 398 defer closer() 399 400 iter := actionsCollection.Find(bson.D{{"receiver", id}}).Iter() 401 for iter.Next(&doc) { 402 actions = append(actions, newAction(st, doc)) 403 } 404 return actions, errors.Trace(iter.Close()) 405 } 406 407 // matchingActionsPending finds actions that match ActionReceiver and 408 // that are pending. 409 func (st *State) matchingActionsPending(ar ActionReceiver) ([]Action, error) { 410 completed := bson.D{{"status", ActionPending}} 411 return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed) 412 } 413 414 // matchingActionsRunning finds actions that match ActionReceiver and 415 // that are running. 416 func (st *State) matchingActionsRunning(ar ActionReceiver) ([]Action, error) { 417 completed := bson.D{{"status", ActionRunning}} 418 return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed) 419 } 420 421 // matchingActionsCompleted finds actions that match ActionReceiver and 422 // that are complete. 423 func (st *State) matchingActionsCompleted(ar ActionReceiver) ([]Action, error) { 424 completed := bson.D{{"$or", []bson.D{ 425 {{"status", ActionCompleted}}, 426 {{"status", ActionCancelled}}, 427 {{"status", ActionFailed}}, 428 }}} 429 return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed) 430 } 431 432 // matchingActionsByReceiverAndStatus finds actionNotifications that 433 // match ActionReceiver. 434 func (st *State) matchingActionsByReceiverAndStatus(tag names.Tag, statusCondition bson.D) ([]Action, error) { 435 var doc actionDoc 436 var actions []Action 437 438 actionsCollection, closer := st.getCollection(actionsC) 439 defer closer() 440 441 sel := append(bson.D{{"receiver", tag.Id()}}, statusCondition...) 442 iter := actionsCollection.Find(sel).Iter() 443 444 for iter.Next(&doc) { 445 actions = append(actions, newAction(st, doc)) 446 } 447 return actions, errors.Trace(iter.Close()) 448 }