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 }