github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/action_test.go (about) 1 // Copyright 2014-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "math/rand" 10 "strings" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/juju/clock/testclock" 16 "github.com/juju/names/v5" 17 jc "github.com/juju/testing/checkers" 18 jujutxn "github.com/juju/txn/v3" 19 gc "gopkg.in/check.v1" 20 21 "github.com/juju/juju/core/actions" 22 "github.com/juju/juju/state" 23 stateerrors "github.com/juju/juju/state/errors" 24 statetesting "github.com/juju/juju/state/testing" 25 coretesting "github.com/juju/juju/testing" 26 "github.com/juju/juju/testing/factory" 27 ) 28 29 type ActionSuite struct { 30 ConnSuite 31 charm *state.Charm 32 actionlessCharm *state.Charm 33 application *state.Application 34 actionlessApplication *state.Application 35 unit *state.Unit 36 unit2 *state.Unit 37 charmlessUnit *state.Unit 38 actionlessUnit *state.Unit 39 model *state.Model 40 } 41 42 var _ = gc.Suite(&ActionSuite{}) 43 44 func (s *ActionSuite) SetUpTest(c *gc.C) { 45 var err error 46 47 s.ConnSuite.SetUpTest(c) 48 49 s.charm = s.AddTestingCharm(c, "dummy") 50 s.actionlessCharm = s.AddTestingCharm(c, "actionless") 51 52 s.application = s.AddTestingApplication(c, "dummy", s.charm) 53 c.Assert(err, jc.ErrorIsNil) 54 s.actionlessApplication = s.AddTestingApplication(c, "actionless", s.actionlessCharm) 55 c.Assert(err, jc.ErrorIsNil) 56 57 sURL, _ := s.application.CharmURL() 58 c.Assert(sURL, gc.NotNil) 59 actionlessSURL, _ := s.actionlessApplication.CharmURL() 60 c.Assert(actionlessSURL, gc.NotNil) 61 62 s.unit, err = s.application.AddUnit(state.AddUnitParams{}) 63 c.Assert(err, jc.ErrorIsNil) 64 c.Assert(s.unit.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"}) 65 66 err = s.unit.SetCharmURL(*sURL) 67 c.Assert(err, jc.ErrorIsNil) 68 69 s.unit2, err = s.application.AddUnit(state.AddUnitParams{}) 70 c.Assert(err, jc.ErrorIsNil) 71 c.Assert(s.unit2.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"}) 72 73 err = s.unit2.SetCharmURL(*sURL) 74 c.Assert(err, jc.ErrorIsNil) 75 76 s.charmlessUnit, err = s.application.AddUnit(state.AddUnitParams{}) 77 c.Assert(err, jc.ErrorIsNil) 78 c.Assert(s.charmlessUnit.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"}) 79 80 s.actionlessUnit, err = s.actionlessApplication.AddUnit(state.AddUnitParams{}) 81 c.Assert(err, jc.ErrorIsNil) 82 c.Assert(s.actionlessUnit.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"}) 83 84 err = s.actionlessUnit.SetCharmURL(*actionlessSURL) 85 c.Assert(err, jc.ErrorIsNil) 86 87 s.model, err = s.State.Model() 88 c.Assert(err, jc.ErrorIsNil) 89 } 90 91 func (s *ActionSuite) TestActionTag(c *gc.C) { 92 operationID, err := s.Model.EnqueueOperation("a test", 1) 93 c.Assert(err, jc.ErrorIsNil) 94 action, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 95 c.Assert(err, jc.ErrorIsNil) 96 97 tag := action.Tag() 98 c.Assert(tag.String(), gc.Equals, "action-"+action.Id()) 99 100 result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted}) 101 c.Assert(err, jc.ErrorIsNil) 102 103 actions, err := s.unit.CompletedActions() 104 c.Assert(err, jc.ErrorIsNil) 105 c.Assert(len(actions), gc.Equals, 1) 106 107 actionResult := actions[0] 108 c.Assert(actionResult, gc.DeepEquals, result) 109 110 tag = actionResult.Tag() 111 c.Assert(tag.String(), gc.Equals, "action-"+actionResult.Id()) 112 } 113 114 func (s *ActionSuite) TestAddAction(c *gc.C) { 115 for i, t := range []struct { 116 should string 117 name string 118 params map[string]interface{} 119 parallel bool 120 executionGroup string 121 whichUnit *state.Unit 122 expectedErr string 123 }{{ 124 should: "enqueue normally", 125 name: "snapshot", 126 whichUnit: s.unit, 127 params: map[string]interface{}{"outfile": "outfile.tar.bz2"}, 128 parallel: true, 129 executionGroup: "group", 130 }, { 131 should: "fail on actionless charms", 132 name: "something", 133 whichUnit: s.actionlessUnit, 134 expectedErr: "no actions defined on charm \"local:quantal/quantal-actionless-1\"", 135 }, { 136 should: "fail on action not defined in schema", 137 whichUnit: s.unit, 138 name: "something-nonexistent", 139 expectedErr: "action \"something-nonexistent\" not defined on unit \"dummy/0\"", 140 }, { 141 should: "invalidate with bad params", 142 whichUnit: s.unit, 143 name: "snapshot", 144 params: map[string]interface{}{ 145 "outfile": 5.0, 146 }, 147 expectedErr: "validation failed: \\(root\\)\\.outfile : must be of type string, given 5", 148 }} { 149 c.Logf("Test %d: should %s", i, t.should) 150 before := state.NowToTheSecond(s.State) 151 later := before.Add(coretesting.LongWait) 152 153 // Copy params over into empty premade map for comparison later 154 params := make(map[string]interface{}) 155 for k, v := range t.params { 156 params[k] = v 157 } 158 159 // Verify we can add an Action 160 operationID, err := s.Model.EnqueueOperation("a test", 1) 161 c.Assert(err, jc.ErrorIsNil) 162 a, err := s.Model.AddAction(t.whichUnit, operationID, t.name, params, &t.parallel, &t.executionGroup) 163 164 if t.expectedErr == "" { 165 c.Assert(err, jc.ErrorIsNil) 166 curl := t.whichUnit.CharmURL() 167 c.Assert(curl, gc.NotNil) 168 ch, _ := s.State.Charm(*curl) 169 schema := ch.Actions() 170 c.Logf("Schema for unit %q:\n%#v", t.whichUnit.Name(), schema) 171 172 // verify we can get it back out by Id 173 model, err := s.State.Model() 174 c.Assert(err, jc.ErrorIsNil) 175 176 action, err := model.Action(a.Id()) 177 c.Assert(err, jc.ErrorIsNil) 178 c.Assert(action, gc.NotNil) 179 c.Check(action.Id(), gc.Equals, a.Id()) 180 c.Check(state.ActionOperationId(action), gc.Equals, operationID) 181 182 // verify we get out what we put in 183 c.Check(action.Name(), gc.Equals, t.name) 184 c.Check(action.Parameters(), jc.DeepEquals, params) 185 c.Check(action.Parallel(), gc.Equals, t.parallel) 186 c.Check(action.ExecutionGroup(), gc.Equals, t.executionGroup) 187 188 // Enqueued time should be within a reasonable time of the beginning 189 // of the test 190 now := state.NowToTheSecond(s.State) 191 c.Check(action.Enqueued(), jc.TimeBetween(before, now)) 192 c.Check(action.Enqueued(), jc.TimeBetween(before, later)) 193 continue 194 } 195 196 c.Check(err, gc.ErrorMatches, t.expectedErr) 197 } 198 } 199 200 func (s *ActionSuite) TestAddActionInsertsDefaults(c *gc.C) { 201 units := make(map[string]*state.Unit) 202 schemas := map[string]string{ 203 "simple": ` 204 act: 205 params: 206 val: 207 type: string 208 default: somestr 209 `[1:], 210 "complicated": ` 211 act: 212 params: 213 val: 214 type: object 215 properties: 216 foo: 217 type: string 218 bar: 219 type: object 220 properties: 221 baz: 222 type: string 223 default: woz 224 `[1:], 225 "none": ` 226 act: 227 params: 228 val: 229 type: string 230 `[1:]} 231 232 // Prepare the units for this test 233 makeUnits(c, s, units, schemas) 234 235 for i, t := range []struct { 236 should string 237 params map[string]interface{} 238 schema string 239 expectedParams map[string]interface{} 240 }{{ 241 should: "do nothing with no defaults", 242 params: map[string]interface{}{}, 243 schema: "none", 244 expectedParams: map[string]interface{}{}, 245 }, { 246 should: "insert a simple default value", 247 params: map[string]interface{}{"foo": "bar"}, 248 schema: "simple", 249 expectedParams: map[string]interface{}{ 250 "foo": "bar", 251 "val": "somestr", 252 }, 253 }, { 254 should: "insert a default value when an empty map is passed", 255 params: map[string]interface{}{}, 256 schema: "simple", 257 expectedParams: map[string]interface{}{ 258 "val": "somestr", 259 }, 260 }, { 261 should: "insert a default value when a nil map is passed", 262 params: nil, 263 schema: "simple", 264 expectedParams: map[string]interface{}{ 265 "val": "somestr", 266 }, 267 }, { 268 should: "insert a nested default value", 269 params: map[string]interface{}{"foo": "bar"}, 270 schema: "complicated", 271 expectedParams: map[string]interface{}{ 272 "foo": "bar", 273 "val": map[string]interface{}{ 274 "bar": map[string]interface{}{ 275 "baz": "woz", 276 }}}, 277 }} { 278 c.Logf("test %d: should %s", i, t.should) 279 u := units[t.schema] 280 // Note that AddAction will only result in errors in the case 281 // of malformed schemas, and schema objects can only be 282 // created from valid schemas. The error handling for this 283 // is tested in the gojsonschema package. 284 operationID, err := s.Model.EnqueueOperation("a test", 1) 285 c.Assert(err, jc.ErrorIsNil) 286 action, err := s.Model.AddAction(u, operationID, "act", t.params, nil, nil) 287 c.Assert(err, jc.ErrorIsNil) 288 c.Check(action.Parameters(), jc.DeepEquals, t.expectedParams) 289 c.Check(action.Parallel(), jc.IsFalse) 290 c.Check(action.ExecutionGroup(), gc.Equals, "") 291 } 292 } 293 294 func (s *ActionSuite) TestActionBeginStartsOperation(c *gc.C) { 295 clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second)) 296 err := s.State.SetClockForTesting(clock) 297 c.Assert(err, jc.ErrorIsNil) 298 299 operationID, err := s.Model.EnqueueOperation("a test", 2) 300 c.Assert(err, jc.ErrorIsNil) 301 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 302 c.Assert(err, jc.ErrorIsNil) 303 anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 304 c.Assert(err, jc.ErrorIsNil) 305 306 anAction, err = anAction.Begin() 307 c.Assert(err, jc.ErrorIsNil) 308 operation, err := s.model.Operation(operationID) 309 c.Assert(err, jc.ErrorIsNil) 310 c.Assert(operation.Status(), gc.Equals, state.ActionRunning) 311 c.Assert(operation.Started(), gc.Equals, anAction.Started()) 312 313 // Starting a second action does not affect the original start time. 314 clock.Advance(5 * time.Second) 315 anAction2, err = anAction2.Begin() 316 c.Assert(err, jc.ErrorIsNil) 317 err = operation.Refresh() 318 c.Assert(err, jc.ErrorIsNil) 319 c.Assert(operation.Status(), gc.Equals, state.ActionRunning) 320 c.Assert(operation.Started(), gc.Equals, anAction.Started()) 321 c.Assert(operation.Started(), gc.Not(gc.Equals), anAction2.Started()) 322 } 323 324 func (s *ActionSuite) TestActionBeginStartsOperationRace(c *gc.C) { 325 clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second)) 326 err := s.State.SetClockForTesting(clock) 327 c.Assert(err, jc.ErrorIsNil) 328 329 operationID, err := s.Model.EnqueueOperation("a test", 2) 330 c.Assert(err, jc.ErrorIsNil) 331 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 332 c.Assert(err, jc.ErrorIsNil) 333 anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 334 c.Assert(err, jc.ErrorIsNil) 335 336 defer state.SetBeforeHooks(c, s.State, func() { 337 clock.Advance(5 * time.Second) 338 anAction2, err = anAction2.Begin() 339 c.Assert(err, jc.ErrorIsNil) 340 })() 341 342 anAction, err = anAction.Begin() 343 c.Assert(err, jc.ErrorIsNil) 344 operation, err := s.model.Operation(operationID) 345 c.Assert(err, jc.ErrorIsNil) 346 347 c.Assert(operation.Status(), gc.Equals, state.ActionRunning) 348 c.Assert(operation.Started(), gc.Equals, anAction2.Started()) 349 c.Assert(operation.Started(), gc.Not(gc.Equals), anAction.Started()) 350 } 351 352 func (s *ActionSuite) TestLastActionFinishCompletesOperation(c *gc.C) { 353 clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second)) 354 err := s.State.SetClockForTesting(clock) 355 c.Assert(err, jc.ErrorIsNil) 356 357 operationID, err := s.Model.EnqueueOperation("a test", 2) 358 c.Assert(err, jc.ErrorIsNil) 359 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 360 c.Assert(err, jc.ErrorIsNil) 361 anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 362 c.Assert(err, jc.ErrorIsNil) 363 364 anAction, err = anAction.Begin() 365 c.Assert(err, jc.ErrorIsNil) 366 clock.Advance(5 * time.Second) 367 anAction2, err = anAction2.Begin() 368 c.Assert(err, jc.ErrorIsNil) 369 370 // Finishing only one action does not complete the operation. 371 _, err = anAction.Finish(state.ActionResults{ 372 Status: state.ActionFailed, 373 }) 374 c.Assert(err, jc.ErrorIsNil) 375 operation, err := s.model.Operation(operationID) 376 c.Assert(err, jc.ErrorIsNil) 377 c.Assert(operation.Status(), gc.Equals, state.ActionRunning) 378 c.Assert(operation.Completed(), gc.Equals, time.Time{}) 379 380 clock.Advance(5 * time.Second) 381 anAction2, err = anAction2.Finish(state.ActionResults{ 382 Status: state.ActionCompleted, 383 }) 384 c.Assert(err, jc.ErrorIsNil) 385 err = operation.Refresh() 386 c.Assert(err, jc.ErrorIsNil) 387 // Failed task precedence over completed. 388 c.Assert(operation.Status(), gc.Equals, state.ActionFailed) 389 c.Assert(operation.Completed(), gc.Equals, anAction2.Completed()) 390 } 391 392 func (s *ActionSuite) TestLastActionFinishCompletesOperationMany(c *gc.C) { 393 numActions := 50 394 395 operationID, err := s.Model.EnqueueOperation("a test", numActions) 396 c.Assert(err, jc.ErrorIsNil) 397 398 wg := sync.WaitGroup{} 399 var actions []state.Action 400 for i := 0; i < numActions; i++ { 401 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 402 c.Assert(err, jc.ErrorIsNil) 403 404 anAction, err = anAction.Begin() 405 c.Assert(err, jc.ErrorIsNil) 406 actions = append(actions, anAction) 407 wg.Add(1) 408 } 409 410 completeCount := int32(0) 411 for i := 0; i < numActions; i++ { 412 go func(a int) { 413 defer func() { 414 atomic.AddInt32(&completeCount, 1) 415 wg.Done() 416 }() 417 time.Sleep(time.Millisecond * time.Duration(rand.Intn(5))) 418 if atomic.LoadInt32(&completeCount) < int32(numActions) { 419 operation, err := s.model.Operation(operationID) 420 c.Assert(err, jc.ErrorIsNil) 421 c.Assert(operation.Status(), gc.Not(gc.Equals), state.ActionCompleted) 422 } 423 424 _, err := actions[a].Finish(state.ActionResults{ 425 Status: state.ActionCompleted, 426 }) 427 c.Assert(err, jc.ErrorIsNil) 428 }(i) 429 } 430 wg.Wait() 431 432 operation, err := s.model.Operation(operationID) 433 c.Assert(err, jc.ErrorIsNil) 434 c.Assert(operation.Status(), gc.Equals, state.ActionCompleted) 435 } 436 437 func (s *ActionSuite) TestLastActionFinishCompletesOperationRace(c *gc.C) { 438 clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second)) 439 err := s.State.SetClockForTesting(clock) 440 c.Assert(err, jc.ErrorIsNil) 441 442 operationID, err := s.Model.EnqueueOperation("a test", 2) 443 c.Assert(err, jc.ErrorIsNil) 444 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 445 c.Assert(err, jc.ErrorIsNil) 446 anAction2, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 447 c.Assert(err, jc.ErrorIsNil) 448 449 anAction, err = anAction.Begin() 450 c.Assert(err, jc.ErrorIsNil) 451 clock.Advance(5 * time.Second) 452 anAction2, err = anAction2.Begin() 453 c.Assert(err, jc.ErrorIsNil) 454 455 operation, err := s.model.Operation(operationID) 456 defer state.SetBeforeHooks(c, s.State, func() { 457 //clock.Advance(5 * time.Second) 458 anAction2, err = anAction2.Finish(state.ActionResults{ 459 Status: state.ActionCancelled, 460 }) 461 c.Assert(err, jc.ErrorIsNil) 462 err = operation.Refresh() 463 c.Assert(err, jc.ErrorIsNil) 464 c.Assert(operation.Status(), gc.Equals, state.ActionRunning) 465 c.Assert(operation.Completed(), gc.Equals, time.Time{}) 466 })() 467 468 // Finishing does complete the operation due to the other action 469 // finishing during the update of this one. 470 anAction, err = anAction.Finish(state.ActionResults{ 471 Status: state.ActionCompleted, 472 }) 473 c.Assert(err, jc.ErrorIsNil) 474 err = operation.Refresh() 475 c.Assert(err, jc.ErrorIsNil) 476 c.Assert(operation.Status(), gc.Equals, state.ActionCancelled) 477 c.Assert(operation.Completed(), gc.Equals, anAction.Completed()) 478 } 479 480 func (s *ActionSuite) TestActionMessages(c *gc.C) { 481 clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second)) 482 err := s.State.SetClockForTesting(clock) 483 c.Assert(err, jc.ErrorIsNil) 484 485 operationID, err := s.Model.EnqueueOperation("a test", 1) 486 c.Assert(err, jc.ErrorIsNil) 487 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 488 c.Assert(err, jc.ErrorIsNil) 489 c.Assert(anAction.Messages(), gc.HasLen, 0) 490 491 // Cannot log messages until action is running. 492 err = anAction.Log("hello") 493 c.Assert(err, gc.ErrorMatches, `cannot log message to task "2" with status pending`) 494 495 anAction, err = anAction.Begin() 496 c.Assert(err, jc.ErrorIsNil) 497 messages := []string{"one", "two", "three"} 498 for i, msg := range messages { 499 err = anAction.Log(msg) 500 c.Assert(err, jc.ErrorIsNil) 501 502 a, err := s.Model.Action(anAction.Id()) 503 c.Assert(err, jc.ErrorIsNil) 504 obtained := a.Messages() 505 c.Assert(obtained, gc.HasLen, i+1) 506 for j, am := range obtained { 507 c.Assert(am.Timestamp(), gc.Equals, clock.Now().UTC()) 508 c.Assert(am.Message(), gc.Equals, messages[j]) 509 } 510 } 511 512 // Cannot log messages after action finishes. 513 _, err = anAction.Finish(state.ActionResults{Status: state.ActionCompleted}) 514 c.Assert(err, jc.ErrorIsNil) 515 err = anAction.Log("hello") 516 c.Assert(err, gc.ErrorMatches, `cannot log message to task "2" with status completed`) 517 } 518 519 func (s *ActionSuite) TestActionLogMessageRace(c *gc.C) { 520 clock := testclock.NewClock(coretesting.NonZeroTime().Round(time.Second)) 521 err := s.State.SetClockForTesting(clock) 522 c.Assert(err, jc.ErrorIsNil) 523 524 operationID, err := s.Model.EnqueueOperation("a test", 1) 525 c.Assert(err, jc.ErrorIsNil) 526 anAction, err := s.Model.AddAction(s.unit, operationID, "snapshot", nil, nil, nil) 527 c.Assert(err, jc.ErrorIsNil) 528 c.Assert(anAction.Messages(), gc.HasLen, 0) 529 530 anAction, err = anAction.Begin() 531 c.Assert(err, jc.ErrorIsNil) 532 533 defer state.SetBeforeHooks(c, s.State, func() { 534 _, err = anAction.Finish(state.ActionResults{Status: state.ActionCompleted}) 535 c.Assert(err, jc.ErrorIsNil) 536 })() 537 538 err = anAction.Log("hello") 539 c.Assert(err, gc.ErrorMatches, `cannot log message to task "2" with status completed`) 540 } 541 542 // makeUnits prepares units with given Action schemas 543 func makeUnits(c *gc.C, s *ActionSuite, units map[string]*state.Unit, schemas map[string]string) { 544 // A few dummy charms that haven't been used yet 545 freeCharms := map[string]string{ 546 "simple": "mysql", 547 "complicated": "mysql-alternative", 548 "none": "wordpress", 549 } 550 551 for name, schema := range schemas { 552 appName := name + "-defaults-application" 553 554 // Add a testing application 555 ch := s.AddActionsCharm(c, freeCharms[name], schema, 1) 556 app := s.AddTestingApplication(c, appName, ch) 557 558 // Get its charm URL 559 sURL, _ := app.CharmURL() 560 c.Assert(sURL, gc.NotNil) 561 562 // Add a unit 563 var err error 564 u, err := app.AddUnit(state.AddUnitParams{}) 565 c.Assert(err, jc.ErrorIsNil) 566 c.Assert(u.Base(), jc.DeepEquals, state.Base{OS: "ubuntu", Channel: "12.10/stable"}) 567 err = u.SetCharmURL(*sURL) 568 c.Assert(err, jc.ErrorIsNil) 569 570 units[name] = u 571 } 572 } 573 574 func (s *ActionSuite) TestEnqueueAction(c *gc.C) { 575 // verify can not enqueue an Action without a name 576 operationID, err := s.Model.EnqueueOperation("a test", 1) 577 c.Assert(err, jc.ErrorIsNil) 578 params := map[string]interface{}{"foo": "bar"} 579 a, err := s.model.EnqueueAction(operationID, s.unit.Tag(), "test", params, true, "group", nil) 580 c.Assert(err, jc.ErrorIsNil) 581 c.Assert(a.Name(), gc.Equals, "test") 582 c.Assert(a.Parameters(), jc.DeepEquals, params) 583 c.Assert(a.Receiver(), gc.Equals, s.unit.Name()) 584 c.Assert(a.Parallel(), jc.IsTrue) 585 c.Assert(a.ExecutionGroup(), gc.Equals, "group") 586 } 587 588 func (s *ActionSuite) TestEnqueueActionRequiresName(c *gc.C) { 589 name := "" 590 591 // verify can not enqueue an Action without a name 592 operationID, err := s.Model.EnqueueOperation("a test", 1) 593 c.Assert(err, jc.ErrorIsNil) 594 _, err = s.model.EnqueueAction(operationID, s.unit.Tag(), name, nil, false, "", nil) 595 c.Assert(err, gc.ErrorMatches, "action name required") 596 } 597 598 func (s *ActionSuite) TestEnqueueActionRequiresValidOperation(c *gc.C) { 599 _, err := s.model.EnqueueAction("666", s.unit.Tag(), "test", nil, false, "", nil) 600 c.Assert(err, gc.ErrorMatches, `operation "666" not found`) 601 } 602 603 func (s *ActionSuite) TestAddActionAcceptsDuplicateNames(c *gc.C) { 604 name := "snapshot" 605 params1 := map[string]interface{}{"outfile": "outfile.tar.bz2"} 606 params2 := map[string]interface{}{"infile": "infile.zip"} 607 608 // verify can add two actions with same name 609 operationID, err := s.Model.EnqueueOperation("a test", 2) 610 c.Assert(err, jc.ErrorIsNil) 611 a1, err := s.Model.AddAction(s.unit, operationID, name, params1, nil, nil) 612 c.Assert(err, jc.ErrorIsNil) 613 614 a2, err := s.Model.AddAction(s.unit, operationID, name, params2, nil, nil) 615 c.Assert(err, jc.ErrorIsNil) 616 617 c.Assert(a1.Id(), gc.Not(gc.Equals), a2.Id()) 618 619 // verify both actually got added 620 actions, err := s.unit.PendingActions() 621 c.Assert(err, jc.ErrorIsNil) 622 c.Assert(len(actions), gc.Equals, 2) 623 624 // verify we can Fail one, retrieve the other, and they're not mixed up 625 model, err := s.State.Model() 626 c.Assert(err, jc.ErrorIsNil) 627 628 action1, err := model.Action(a1.Id()) 629 c.Assert(err, jc.ErrorIsNil) 630 _, err = action1.Finish(state.ActionResults{Status: state.ActionFailed}) 631 c.Assert(err, jc.ErrorIsNil) 632 633 action2, err := model.Action(a2.Id()) 634 c.Assert(err, jc.ErrorIsNil) 635 c.Assert(action2.Parameters(), jc.DeepEquals, params2) 636 637 // verify only one left, and it's the expected one 638 actions, err = s.unit.PendingActions() 639 c.Assert(err, jc.ErrorIsNil) 640 c.Assert(len(actions), gc.Equals, 1) 641 c.Assert(actions[0].Id(), gc.Equals, a2.Id()) 642 } 643 644 func (s *ActionSuite) TestAddActionLifecycle(c *gc.C) { 645 unit, err := s.State.Unit(s.unit.Name()) 646 c.Assert(err, jc.ErrorIsNil) 647 preventUnitDestroyRemove(c, unit) 648 649 // make unit state Dying 650 err = unit.Destroy() 651 c.Assert(err, jc.ErrorIsNil) 652 653 // can add action to a dying unit 654 operationID, err := s.Model.EnqueueOperation("a test", 2) 655 c.Assert(err, jc.ErrorIsNil) 656 _, err = s.Model.AddAction(unit, operationID, "snapshot", map[string]interface{}{}, nil, nil) 657 c.Assert(err, jc.ErrorIsNil) 658 659 // make sure unit is dead 660 err = unit.EnsureDead() 661 c.Assert(err, jc.ErrorIsNil) 662 663 // cannot add action to a dead unit 664 _, err = s.Model.AddAction(unit, operationID, "snapshot", map[string]interface{}{}, nil, nil) 665 c.Assert(err, gc.Equals, stateerrors.ErrDead) 666 } 667 668 func (s *ActionSuite) TestAddActionFailsOnDeadUnitInTransaction(c *gc.C) { 669 unit, err := s.State.Unit(s.unit.Name()) 670 c.Assert(err, jc.ErrorIsNil) 671 preventUnitDestroyRemove(c, unit) 672 673 killUnit := jujutxn.TestHook{ 674 Before: func() { 675 c.Assert(unit.Destroy(), gc.IsNil) 676 c.Assert(unit.EnsureDead(), gc.IsNil) 677 }, 678 } 679 defer state.SetTestHooks(c, s.State, killUnit).Check() 680 681 operationID, err := s.Model.EnqueueOperation("a test", 1) 682 c.Assert(err, jc.ErrorIsNil) 683 _, err = s.Model.AddAction(unit, operationID, "snapshot", map[string]interface{}{}, nil, nil) 684 c.Assert(err, gc.Equals, stateerrors.ErrDead) 685 } 686 687 func (s *ActionSuite) TestFail(c *gc.C) { 688 // get unit, add an action, retrieve that action 689 unit, err := s.State.Unit(s.unit.Name()) 690 c.Assert(err, jc.ErrorIsNil) 691 preventUnitDestroyRemove(c, unit) 692 693 operationID, err := s.Model.EnqueueOperation("a test", 1) 694 c.Assert(err, jc.ErrorIsNil) 695 a, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil) 696 c.Assert(err, jc.ErrorIsNil) 697 698 model, err := s.State.Model() 699 c.Assert(err, jc.ErrorIsNil) 700 701 action, err := model.Action(a.Id()) 702 c.Assert(err, jc.ErrorIsNil) 703 704 // ensure no action results for this action 705 results, err := unit.CompletedActions() 706 c.Assert(err, jc.ErrorIsNil) 707 c.Assert(len(results), gc.Equals, 0) 708 709 // fail the action, and verify that it succeeds 710 reason := "test fail reason" 711 result, err := action.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason}) 712 c.Assert(err, jc.ErrorIsNil) 713 714 // ensure we now have a result for this action 715 results, err = unit.CompletedActions() 716 c.Assert(err, jc.ErrorIsNil) 717 c.Assert(len(results), gc.Equals, 1) 718 c.Assert(results[0], gc.DeepEquals, result) 719 720 c.Assert(results[0].Name(), gc.Equals, action.Name()) 721 c.Assert(results[0].Status(), gc.Equals, state.ActionFailed) 722 723 // Verify the Action Completed time was within a reasonable 724 // time of the Enqueued time. 725 diff := results[0].Completed().Sub(action.Enqueued()) 726 c.Assert(diff >= 0, jc.IsTrue) 727 c.Assert(diff < coretesting.LongWait, jc.IsTrue) 728 729 res, errstr := results[0].Results() 730 c.Assert(errstr, gc.Equals, reason) 731 c.Assert(res, gc.DeepEquals, map[string]interface{}{}) 732 733 // validate that a pending action is no longer returned by UnitActions. 734 actions, err := unit.PendingActions() 735 c.Assert(err, jc.ErrorIsNil) 736 c.Assert(len(actions), gc.Equals, 0) 737 } 738 739 func (s *ActionSuite) TestErrorAfterEnqueuingFail(c *gc.C) { 740 // get unit, add an action, retrieve that action 741 unit, err := s.State.Unit(s.unit.Name()) 742 c.Assert(err, jc.ErrorIsNil) 743 preventUnitDestroyRemove(c, unit) 744 745 unit2, err := s.State.Unit(s.unit2.Name()) 746 c.Assert(err, jc.ErrorIsNil) 747 preventUnitDestroyRemove(c, unit2) 748 749 operationID, err := s.Model.EnqueueOperation("enqueuing test", 3) 750 c.Assert(err, jc.ErrorIsNil) 751 a, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil) 752 c.Assert(err, jc.ErrorIsNil) 753 a2, err := s.Model.AddAction(unit2, operationID, "snapshot", nil, nil, nil) 754 c.Assert(err, jc.ErrorIsNil) 755 756 err = s.model.FailOperationEnqueuing(operationID, "fail for test", 2) 757 c.Assert(err, jc.ErrorIsNil) 758 759 model, err := s.State.Model() 760 c.Assert(err, jc.ErrorIsNil) 761 762 action, err := model.Action(a.Id()) 763 c.Assert(err, jc.ErrorIsNil) 764 action2, err := model.Action(a2.Id()) 765 c.Assert(err, jc.ErrorIsNil) 766 767 // complete the action, and verify that it succeeds 768 output := map[string]interface{}{"output": "action ran successfully"} 769 _, err = action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output}) 770 c.Assert(err, jc.ErrorIsNil) 771 _, err = action2.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output}) 772 c.Assert(err, jc.ErrorIsNil) 773 774 operation, err := s.model.Operation(operationID) 775 c.Assert(err, jc.ErrorIsNil) 776 c.Assert(operation.Status(), gc.Equals, state.ActionError) 777 c.Assert(operation.Fail(), gc.Equals, "fail for test") 778 } 779 780 func (s *ActionSuite) TestComplete(c *gc.C) { 781 // get unit, add an action, retrieve that action 782 unit, err := s.State.Unit(s.unit.Name()) 783 c.Assert(err, jc.ErrorIsNil) 784 preventUnitDestroyRemove(c, unit) 785 786 operationID, err := s.Model.EnqueueOperation("a test", 1) 787 c.Assert(err, jc.ErrorIsNil) 788 a, err := s.Model.AddAction(unit, operationID, "snapshot", nil, nil, nil) 789 c.Assert(err, jc.ErrorIsNil) 790 791 model, err := s.State.Model() 792 c.Assert(err, jc.ErrorIsNil) 793 794 action, err := model.Action(a.Id()) 795 c.Assert(err, jc.ErrorIsNil) 796 797 // ensure no action results for this action 798 results, err := unit.CompletedActions() 799 c.Assert(err, jc.ErrorIsNil) 800 c.Assert(len(results), gc.Equals, 0) 801 802 // complete the action, and verify that it succeeds 803 output := map[string]interface{}{"output": "action ran successfully"} 804 result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output}) 805 c.Assert(err, jc.ErrorIsNil) 806 807 // ensure we now have a result for this action 808 results, err = unit.CompletedActions() 809 c.Assert(err, jc.ErrorIsNil) 810 c.Assert(len(results), gc.Equals, 1) 811 c.Assert(results[0], gc.DeepEquals, result) 812 813 c.Assert(results[0].Name(), gc.Equals, action.Name()) 814 c.Assert(results[0].Status(), gc.Equals, state.ActionCompleted) 815 res, errstr := results[0].Results() 816 c.Assert(errstr, gc.Equals, "") 817 c.Assert(res, gc.DeepEquals, output) 818 819 // validate that a pending action is no longer returned by UnitActions. 820 actions, err := unit.PendingActions() 821 c.Assert(err, jc.ErrorIsNil) 822 c.Assert(len(actions), gc.Equals, 0) 823 } 824 825 func (s *ActionSuite) TestFindActionsByName(c *gc.C) { 826 actions := []struct { 827 Name string 828 Parameters map[string]interface{} 829 }{ 830 {Name: "action-1", Parameters: map[string]interface{}{}}, 831 {Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}}, 832 {Name: "action-1", Parameters: map[string]interface{}{"yeah": true, "take": nil}}, 833 {Name: "action-9", Parameters: map[string]interface{}{"district": 9}}, 834 {Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}}, 835 } 836 837 operationID, err := s.Model.EnqueueOperation("a test", len(actions)) 838 c.Assert(err, jc.ErrorIsNil) 839 for _, action := range actions { 840 _, err := s.model.EnqueueAction(operationID, s.unit.Tag(), action.Name, action.Parameters, false, "", nil) 841 c.Assert(err, gc.Equals, nil) 842 } 843 844 results, err := s.model.FindActionsByName("action-1") 845 c.Assert(err, jc.ErrorIsNil) 846 847 c.Assert(len(results), gc.Equals, 2) 848 for _, result := range results { 849 c.Check(result.Name(), gc.Equals, "action-1") 850 } 851 } 852 853 func (s *ActionSuite) TestActionsWatcherEmitsInitialChanges(c *gc.C) { 854 // LP-1391914 :: idPrefixWatcher fails watcher contract to send 855 // initial Change event 856 // 857 // state/idPrefixWatcher does not send an initial event in response 858 // to the first time Changes() is called if all of the pending 859 // events are removed before the first consumption of Changes(). 860 // The watcher contract specifies that the first call to Changes() 861 // should always return at a minimum an empty change set to notify 862 // clients of it's initial state 863 864 // preamble 865 app := s.AddTestingApplication(c, "dummy3", s.charm) 866 unit, err := app.AddUnit(state.AddUnitParams{}) 867 c.Assert(err, jc.ErrorIsNil) 868 u, err := s.State.Unit(unit.Name()) 869 c.Assert(err, jc.ErrorIsNil) 870 preventUnitDestroyRemove(c, u) 871 872 operationID, err := s.Model.EnqueueOperation("a test", 2) 873 c.Assert(err, jc.ErrorIsNil) 874 // queue up actions 875 a1, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil) 876 c.Assert(err, jc.ErrorIsNil) 877 a2, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil) 878 c.Assert(err, jc.ErrorIsNil) 879 880 // start watcher but don't consume Changes() yet 881 w := u.WatchPendingActionNotifications() 882 defer statetesting.AssertStop(c, w) 883 wc := statetesting.NewStringsWatcherC(c, w) 884 885 // remove actions 886 reason := "removed" 887 _, err = a1.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason}) 888 c.Assert(err, jc.ErrorIsNil) 889 _, err = a2.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason}) 890 c.Assert(err, jc.ErrorIsNil) 891 892 // per contract, there should be at minimum an initial empty Change() result 893 wc.AssertChangeMaybeIncluding(expectActionIds(a1, a2)...) 894 wc.AssertNoChange() 895 } 896 897 func (s *ActionSuite) TestUnitWatchActionNotifications(c *gc.C) { 898 // get units 899 unit1, err := s.State.Unit(s.unit.Name()) 900 c.Assert(err, jc.ErrorIsNil) 901 preventUnitDestroyRemove(c, unit1) 902 903 unit2, err := s.State.Unit(s.unit2.Name()) 904 c.Assert(err, jc.ErrorIsNil) 905 preventUnitDestroyRemove(c, unit2) 906 907 // queue some actions before starting the watcher 908 operationID, err := s.Model.EnqueueOperation("a test", 2) 909 c.Assert(err, jc.ErrorIsNil) 910 fa1, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil) 911 c.Assert(err, jc.ErrorIsNil) 912 fa2, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil) 913 c.Assert(err, jc.ErrorIsNil) 914 s.WaitForModelWatchersIdle(c, s.State.ModelUUID()) 915 916 // set up watcher on first unit 917 w := unit1.WatchPendingActionNotifications() 918 defer statetesting.AssertStop(c, w) 919 wc := statetesting.NewStringsWatcherC(c, w) 920 // make sure the previously pending actions are sent on the watcher 921 expect := expectActionIds(fa1, fa2) 922 wc.AssertChange(expect...) 923 wc.AssertNoChange() 924 925 // add watcher on unit2 926 w2 := unit2.WatchPendingActionNotifications() 927 defer statetesting.AssertStop(c, w2) 928 wc2 := statetesting.NewStringsWatcherC(c, w2) 929 wc2.AssertChange() 930 wc2.AssertNoChange() 931 932 // add action on unit2 and makes sure unit1 watcher doesn't trigger 933 // and unit2 watcher does 934 fa3, err := s.Model.AddAction(unit2, operationID, "snapshot", nil, nil, nil) 935 c.Assert(err, jc.ErrorIsNil) 936 wc.AssertNoChange() 937 expect2 := expectActionIds(fa3) 938 wc2.AssertChange(expect2...) 939 wc2.AssertNoChange() 940 941 // add a couple actions on unit1 and make sure watcher sees events 942 fa4, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil) 943 c.Assert(err, jc.ErrorIsNil) 944 fa5, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil) 945 c.Assert(err, jc.ErrorIsNil) 946 947 expect = expectActionIds(fa4, fa5) 948 wc.AssertChange(expect...) 949 wc.AssertNoChange() 950 } 951 952 func (s *ActionSuite) TestMergeIds(c *gc.C) { 953 var tests = []struct { 954 changes string 955 adds string 956 removes string 957 expected string 958 }{ 959 {changes: "", adds: "a0,a1", removes: "", expected: "a0,a1"}, 960 {changes: "a0,a1", adds: "", removes: "a0", expected: "a1"}, 961 {changes: "a0,a1", adds: "a2", removes: "a0", expected: "a1,a2"}, 962 963 {changes: "", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 964 {changes: "", adds: "a0,a1,a2", removes: "a0,a1,a2", expected: ""}, 965 966 {changes: "a0", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 967 {changes: "a1", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 968 {changes: "a2", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 969 970 {changes: "a3,a4", adds: "a1,a4,a5", removes: "a1,a3", expected: "a4,a5"}, 971 {changes: "a0,a1,a2", adds: "a1,a4,a5", removes: "a1,a3", expected: "a0,a2,a4,a5"}, 972 } 973 974 prefix := state.DocID(s.State, "") 975 976 for ix, test := range tests { 977 updates := mapify(prefix, test.adds, test.removes) 978 changes := sliceify("", test.changes) 979 expected := sliceify("", test.expected) 980 981 c.Log(fmt.Sprintf("test number %d %#v", ix, test)) 982 err := state.WatcherMergeIds(&changes, updates, state.MakeActionIdConverter(s.State)) 983 c.Assert(err, jc.ErrorIsNil) 984 c.Assert(changes, jc.SameContents, expected) 985 } 986 } 987 988 func (s *ActionSuite) TestMergeIdsErrors(c *gc.C) { 989 990 var tests = []struct { 991 name string 992 key interface{} 993 }{ 994 {name: "bool", key: true}, 995 {name: "int", key: 0}, 996 {name: "chan string", key: make(chan string)}, 997 } 998 999 for _, test := range tests { 1000 changes, updates := []string{}, map[interface{}]bool{} 1001 updates[test.key] = true 1002 err := state.WatcherMergeIds(&changes, updates, state.MakeActionIdConverter(s.State)) 1003 c.Assert(err, gc.ErrorMatches, "id is not of type string, got "+test.name) 1004 } 1005 } 1006 1007 func (s *ActionSuite) TestEnsureSuffix(c *gc.C) { 1008 marker := "-marker-" 1009 fn := state.WatcherEnsureSuffixFn(marker) 1010 c.Assert(fn, gc.Not(gc.IsNil)) 1011 1012 var tests = []struct { 1013 given string 1014 expect string 1015 }{ 1016 {given: marker, expect: marker}, 1017 {given: "", expect: "" + marker}, 1018 {given: "asdf", expect: "asdf" + marker}, 1019 {given: "asdf" + marker, expect: "asdf" + marker}, 1020 {given: "asdf" + marker + "qwerty", expect: "asdf" + marker + "qwerty" + marker}, 1021 } 1022 1023 for _, test := range tests { 1024 c.Assert(fn(test.given), gc.Equals, test.expect) 1025 } 1026 } 1027 1028 func (s *ActionSuite) TestMakeIdFilter(c *gc.C) { 1029 marker := "-marker-" 1030 badmarker := "-bad-" 1031 fn := state.WatcherMakeIdFilter(s.State, marker) 1032 c.Assert(fn, gc.IsNil) 1033 1034 ar1 := mockAR{id: "mock/1"} 1035 ar2 := mockAR{id: "mock/2"} 1036 fn = state.WatcherMakeIdFilter(s.State, marker, ar1, ar2) 1037 c.Assert(fn, gc.Not(gc.IsNil)) 1038 1039 var tests = []struct { 1040 id string 1041 match bool 1042 }{ 1043 {id: "mock/1" + marker + "", match: true}, 1044 {id: "mock/1" + marker + "asdf", match: true}, 1045 {id: "mock/2" + marker + "", match: true}, 1046 {id: "mock/2" + marker + "asdf", match: true}, 1047 1048 {id: "mock/1" + badmarker + "", match: false}, 1049 {id: "mock/1" + badmarker + "asdf", match: false}, 1050 {id: "mock/2" + badmarker + "", match: false}, 1051 {id: "mock/2" + badmarker + "asdf", match: false}, 1052 1053 {id: "mock/1" + marker + "0", match: true}, 1054 {id: "mock/10" + marker + "0", match: false}, 1055 {id: "mock/2" + marker + "0", match: true}, 1056 {id: "mock/20" + marker + "0", match: false}, 1057 {id: "mock" + marker + "0", match: false}, 1058 1059 {id: "" + marker + "0", match: false}, 1060 {id: "mock/1-0", match: false}, 1061 {id: "mock/1-0", match: false}, 1062 } 1063 1064 for _, test := range tests { 1065 c.Assert(fn(state.DocID(s.State, test.id)), gc.Equals, test.match) 1066 } 1067 } 1068 1069 func (s *ActionSuite) TestWatchActionNotifications(c *gc.C) { 1070 app := s.AddTestingApplication(c, "dummy2", s.charm) 1071 u, err := app.AddUnit(state.AddUnitParams{}) 1072 c.Assert(err, jc.ErrorIsNil) 1073 1074 w := u.WatchPendingActionNotifications() 1075 defer statetesting.AssertStop(c, w) 1076 wc := statetesting.NewStringsWatcherC(c, w) 1077 wc.AssertChange() 1078 wc.AssertNoChange() 1079 1080 // add 3 actions 1081 operationID, err := s.Model.EnqueueOperation("a test", 3) 1082 c.Assert(err, jc.ErrorIsNil) 1083 fa1, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil) 1084 c.Assert(err, jc.ErrorIsNil) 1085 fa2, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil) 1086 c.Assert(err, jc.ErrorIsNil) 1087 fa3, err := s.Model.AddAction(u, operationID, "snapshot", nil, nil, nil) 1088 c.Assert(err, jc.ErrorIsNil) 1089 1090 model, err := s.State.Model() 1091 c.Assert(err, jc.ErrorIsNil) 1092 1093 // TODO(quiescence): this is a bit racey due to the unpredictable nature of mongo change streams 1094 // once we have some quiescence built into the watcher, we can re-enable this. 1095 // fail the middle one 1096 _ = model 1097 // action, err := model.Action(fa2.Id()) 1098 // c.Assert(err, jc.ErrorIsNil) 1099 // _, err = action.Finish(state.ActionResults{Status: state.ActionFailed, Message: "die scum"}) 1100 // c.Assert(err, jc.ErrorIsNil) 1101 1102 // we expect them all even though the second one has already failed. 1103 // TODO(quiescence): reimplement some quiescence on the PendingActionNotications watcher 1104 expect := expectActionIds(fa1, fa2, fa3) 1105 wc.AssertChange(expect...) 1106 wc.AssertNoChange() 1107 } 1108 1109 func expectActionIds(actions ...state.Action) []string { 1110 ids := make([]string, len(actions)) 1111 for i, action := range actions { 1112 ids[i] = action.Id() 1113 } 1114 return ids 1115 } 1116 1117 func (s *ActionSuite) TestWatchActionLogs(c *gc.C) { 1118 unit1, err := s.State.Unit(s.unit.Name()) 1119 c.Assert(err, jc.ErrorIsNil) 1120 1121 operationID, err := s.Model.EnqueueOperation("a test", 1) 1122 c.Assert(err, jc.ErrorIsNil) 1123 // queue some actions before starting the watcher 1124 fa1, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil) 1125 c.Assert(err, jc.ErrorIsNil) 1126 fa1, err = fa1.Begin() 1127 c.Assert(err, jc.ErrorIsNil) 1128 err = fa1.Log("first") 1129 c.Assert(err, jc.ErrorIsNil) 1130 1131 // Ensure no cross contamination - add another action. 1132 fa2, err := s.Model.AddAction(unit1, operationID, "snapshot", nil, nil, nil) 1133 c.Assert(err, jc.ErrorIsNil) 1134 fa2, err = fa2.Begin() 1135 c.Assert(err, jc.ErrorIsNil) 1136 err = fa2.Log("another") 1137 c.Assert(err, jc.ErrorIsNil) 1138 1139 s.WaitForModelWatchersIdle(c, s.State.ModelUUID()) 1140 1141 startNow := time.Now().UTC() 1142 makeTimestamp := func(offset time.Duration) time.Time { 1143 return time.Unix(0, startNow.UnixNano()).Add(offset).UTC() 1144 } 1145 1146 checkExpected := func(wc statetesting.StringsWatcherC, expected []actions.ActionMessage) { 1147 var ch []string 1148 for len(ch) < len(expected) { 1149 select { 1150 case changes := <-wc.Watcher.Changes(): 1151 ch = append(ch, changes...) 1152 case <-time.After(coretesting.LongWait): 1153 c.Fatalf("watcher did not send change") 1154 } 1155 } 1156 var msg []actions.ActionMessage 1157 for i, chStr := range ch { 1158 var gotMessage actions.ActionMessage 1159 err := json.Unmarshal([]byte(chStr), &gotMessage) 1160 c.Assert(err, jc.ErrorIsNil) 1161 // We can't control the actual time so check for 1162 // not nil and then assigned to a known value. 1163 c.Assert(gotMessage.Timestamp, gc.NotNil) 1164 gotMessage.Timestamp = makeTimestamp(time.Duration(i) * time.Second) 1165 msg = append(msg, gotMessage) 1166 } 1167 c.Assert(msg, jc.DeepEquals, expected) 1168 wc.AssertNoChange() 1169 } 1170 1171 w := s.State.WatchActionLogs(fa1.Id()) 1172 defer statetesting.AssertStop(c, w) 1173 wc := statetesting.NewStringsWatcherC(c, w) 1174 // make sure the previously pending actions are sent on the watcher 1175 expected := []actions.ActionMessage{{ 1176 Timestamp: startNow, 1177 Message: "first", 1178 }} 1179 checkExpected(wc, expected) 1180 1181 // Add 3 more messages; we should only see those on this watcher. 1182 err = fa1.Log("another") 1183 c.Assert(err, jc.ErrorIsNil) 1184 1185 err = fa1.Log("yet another") 1186 c.Assert(err, jc.ErrorIsNil) 1187 1188 expected = []actions.ActionMessage{{ 1189 Timestamp: startNow, 1190 Message: "another", 1191 }, { 1192 Timestamp: makeTimestamp(1 * time.Second), 1193 Message: "yet another", 1194 }} 1195 checkExpected(wc, expected) 1196 1197 // Add the 3rd message separately to ensure the 1198 // tracking of already reported messages works. 1199 err = fa1.Log("and yet another") 1200 c.Assert(err, jc.ErrorIsNil) 1201 expected = []actions.ActionMessage{{ 1202 Timestamp: makeTimestamp(0 * time.Second), 1203 Message: "and yet another", 1204 }} 1205 checkExpected(wc, expected) 1206 1207 // But on a new watcher we see all 3 events. 1208 w2 := s.State.WatchActionLogs(fa1.Id()) 1209 defer statetesting.AssertStop(c, w) 1210 wc2 := statetesting.NewStringsWatcherC(c, w2) 1211 // Make sure the previously pending actions are sent on the watcher. 1212 expected = []actions.ActionMessage{{ 1213 Timestamp: startNow, 1214 Message: "first", 1215 }, { 1216 Timestamp: makeTimestamp(1 * time.Second), 1217 Message: "another", 1218 }, { 1219 Timestamp: makeTimestamp(2 * time.Second), 1220 Message: "yet another", 1221 }, { 1222 Timestamp: makeTimestamp(3 * time.Second), 1223 Message: "and yet another", 1224 }} 1225 checkExpected(wc2, expected) 1226 } 1227 1228 // mapify is a convenience method, also to make reading the tests 1229 // easier. It combines two comma delimited strings representing 1230 // additions and removals and turns it into the map[interface{}]bool 1231 // format needed 1232 func mapify(prefix, adds, removes string) map[interface{}]bool { 1233 m := map[interface{}]bool{} 1234 for _, v := range sliceify(prefix, adds) { 1235 m[v] = true 1236 } 1237 for _, v := range sliceify(prefix, removes) { 1238 m[v] = false 1239 } 1240 return m 1241 } 1242 1243 // sliceify turns a comma separated list of strings into a slice 1244 // trimming white space and excluding empty strings. 1245 func sliceify(prefix, csvlist string) []string { 1246 slice := []string{} 1247 if csvlist == "" { 1248 return slice 1249 } 1250 for _, entry := range strings.Split(csvlist, ",") { 1251 clean := strings.TrimSpace(entry) 1252 if clean != "" { 1253 slice = append(slice, prefix+clean) 1254 } 1255 } 1256 return slice 1257 } 1258 1259 // mockAR is an implementation of ActionReceiver that can be used for 1260 // testing that requires the ActionReceiver.Tag() call to return a 1261 // names.Tag 1262 type mockAR struct { 1263 id string 1264 } 1265 1266 var _ state.ActionReceiver = (*mockAR)(nil) 1267 1268 func (r mockAR) PrepareActionPayload(_ string, _ map[string]interface{}, _ *bool, _ *string) (map[string]interface{}, bool, string, error) { 1269 return nil, false, "", nil 1270 } 1271 func (r mockAR) CancelAction(_ state.Action) (state.Action, error) { return nil, nil } 1272 func (r mockAR) WatchActionNotifications() state.StringsWatcher { return nil } 1273 func (r mockAR) WatchPendingActionNotifications() state.StringsWatcher { return nil } 1274 func (r mockAR) Actions() ([]state.Action, error) { return nil, nil } 1275 func (r mockAR) CompletedActions() ([]state.Action, error) { return nil, nil } 1276 func (r mockAR) PendingActions() ([]state.Action, error) { return nil, nil } 1277 func (r mockAR) RunningActions() ([]state.Action, error) { return nil, nil } 1278 func (r mockAR) Tag() names.Tag { return names.NewUnitTag(r.id) } 1279 1280 type ActionPruningSuite struct { 1281 statetesting.StateWithWallClockSuite 1282 } 1283 1284 var _ = gc.Suite(&ActionPruningSuite{}) 1285 1286 func (s *ActionPruningSuite) TestPruneOperationsBySize(c *gc.C) { 1287 clock := testclock.NewClock(coretesting.NonZeroTime()) 1288 err := s.State.SetClockForTesting(clock) 1289 c.Assert(err, jc.ErrorIsNil) 1290 application := s.Factory.MakeApplication(c, nil) 1291 unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 1292 1293 // PrimeOperations generates the operations and tasks to be pruned. 1294 const numOperationEntries = 15 // At slightly > 500kB per entry 1295 const tasksPerOperation = 2 1296 const maxLogSize = 5 //MB 1297 state.PrimeOperations(c, clock.Now(), unit, numOperationEntries, tasksPerOperation) 1298 1299 actions, err := unit.Actions() 1300 c.Assert(err, jc.ErrorIsNil) 1301 c.Assert(actions, gc.HasLen, tasksPerOperation*numOperationEntries) 1302 ops, err := s.Model.AllOperations() 1303 c.Assert(err, jc.ErrorIsNil) 1304 c.Assert(ops, gc.HasLen, numOperationEntries) 1305 1306 var stop <-chan struct{} 1307 err = state.PruneOperations(stop, s.State, 0, maxLogSize) 1308 c.Assert(err, jc.ErrorIsNil) 1309 1310 actions, err = unit.Actions() 1311 c.Assert(err, jc.ErrorIsNil) 1312 ops, err = s.Model.AllOperations() 1313 c.Assert(err, jc.ErrorIsNil) 1314 1315 // The test here is to see if the remaining count is relatively close to 1316 // the max log size x 2. I would expect the number of remaining entries to 1317 // be no greater than 2 x 1.5 x the max log size in MB since each entry is 1318 // about 500kB (in memory) in size. 1.5x is probably good enough to ensure 1319 // this test doesn't flake. 1320 c.Assert(float64(len(actions)), jc.LessThan, 2.0*maxLogSize*1.5) 1321 c.Assert(float64(len(ops)), gc.Equals, float64(len(actions))/2.0) 1322 } 1323 1324 func (s *ActionPruningSuite) TestPruneOperationsBySizeOldestFirst(c *gc.C) { 1325 clock := testclock.NewClock(coretesting.NonZeroTime()) 1326 err := s.State.SetClockForTesting(clock) 1327 c.Assert(err, jc.ErrorIsNil) 1328 application := s.Factory.MakeApplication(c, nil) 1329 unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 1330 1331 const numOperationEntriesOlder = 5 1332 const numOperationEntriesYounger = 5 1333 const tasksPerOperation = 3 1334 const numOperationEntries = numOperationEntriesOlder + numOperationEntriesYounger 1335 const maxLogSize = 5 //MB 1336 1337 olderTime := clock.Now().Add(-1 * time.Hour) 1338 youngerTime := clock.Now() 1339 1340 state.PrimeOperations(c, olderTime, unit, numOperationEntriesOlder, tasksPerOperation) 1341 state.PrimeOperations(c, youngerTime, unit, numOperationEntriesYounger, tasksPerOperation) 1342 1343 actions, err := unit.Actions() 1344 c.Assert(err, jc.ErrorIsNil) 1345 c.Assert(actions, gc.HasLen, tasksPerOperation*numOperationEntries) 1346 ops, err := s.Model.AllOperations() 1347 c.Assert(err, jc.ErrorIsNil) 1348 c.Assert(ops, gc.HasLen, numOperationEntries) 1349 1350 var stop <-chan struct{} 1351 err = state.PruneOperations(stop, s.State, 0, maxLogSize) 1352 c.Assert(err, jc.ErrorIsNil) 1353 1354 actions, err = unit.Actions() 1355 c.Assert(err, jc.ErrorIsNil) 1356 1357 var olderEntries []time.Time 1358 var youngerEntries []time.Time 1359 for _, entry := range actions { 1360 if entry.Completed().Before(youngerTime.Round(time.Second)) { 1361 olderEntries = append(olderEntries, entry.Completed()) 1362 } else { 1363 youngerEntries = append(youngerEntries, entry.Completed()) 1364 } 1365 } 1366 c.Assert(len(youngerEntries), jc.GreaterThan, len(olderEntries)) 1367 1368 _, err = s.Model.AllOperations() 1369 c.Assert(err, jc.ErrorIsNil) 1370 olderEntries = nil 1371 youngerEntries = nil 1372 for _, entry := range actions { 1373 if entry.Completed().Before(youngerTime.Round(time.Second)) { 1374 olderEntries = append(olderEntries, entry.Completed()) 1375 } else { 1376 youngerEntries = append(youngerEntries, entry.Completed()) 1377 } 1378 } 1379 c.Assert(len(youngerEntries), jc.GreaterThan, len(olderEntries)) 1380 } 1381 1382 func (s *ActionPruningSuite) TestPruneOperationsBySizeKeepsIncomplete(c *gc.C) { 1383 clock := testclock.NewClock(coretesting.NonZeroTime()) 1384 err := s.State.SetClockForTesting(clock) 1385 c.Assert(err, jc.ErrorIsNil) 1386 application := s.Factory.MakeApplication(c, nil) 1387 unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 1388 1389 const numOperationEntriesOlder = 5 1390 const numOperationEntriesYounger = 5 1391 const numOperationEntriesIncomplete = 5 1392 const tasksPerOperation = 3 1393 const numOperationEntries = numOperationEntriesOlder + numOperationEntriesYounger + numOperationEntriesIncomplete 1394 const maxLogSize = 5 //MB 1395 1396 olderTime := clock.Now().Add(-1 * time.Hour) 1397 youngerTime := clock.Now() 1398 1399 state.PrimeOperations(c, time.Time{}, unit, numOperationEntriesIncomplete, tasksPerOperation) 1400 state.PrimeOperations(c, olderTime, unit, numOperationEntriesOlder, tasksPerOperation) 1401 state.PrimeOperations(c, youngerTime, unit, numOperationEntriesYounger, tasksPerOperation) 1402 1403 actions, err := unit.Actions() 1404 c.Assert(err, jc.ErrorIsNil) 1405 c.Assert(actions, gc.HasLen, tasksPerOperation*numOperationEntries) 1406 ops, err := s.Model.AllOperations() 1407 c.Assert(err, jc.ErrorIsNil) 1408 c.Assert(ops, gc.HasLen, numOperationEntries) 1409 1410 var stop <-chan struct{} 1411 err = state.PruneOperations(stop, s.State, 0, maxLogSize) 1412 c.Assert(err, jc.ErrorIsNil) 1413 1414 actions, err = unit.Actions() 1415 c.Assert(err, jc.ErrorIsNil) 1416 1417 var olderEntries []time.Time 1418 var youngerEntries []time.Time 1419 var incompleteEntries []time.Time 1420 zero := time.Time{} 1421 for _, entry := range actions { 1422 if entry.Completed() == zero { 1423 incompleteEntries = append(incompleteEntries, entry.Completed()) 1424 } else if entry.Completed().Before(youngerTime.Round(time.Second)) { 1425 olderEntries = append(olderEntries, entry.Completed()) 1426 } else { 1427 youngerEntries = append(youngerEntries, entry.Completed()) 1428 } 1429 } 1430 c.Assert(youngerEntries, gc.HasLen, 0) 1431 c.Assert(olderEntries, gc.HasLen, 0) 1432 c.Assert(len(incompleteEntries), gc.Not(gc.Equals), 0) 1433 1434 ops, err = s.Model.AllOperations() 1435 c.Assert(err, jc.ErrorIsNil) 1436 1437 // The test here is to see if the remaining count is relatively close to 1438 // the max log size x 2. I would expect the number of remaining entries to 1439 // be no greater than 2 x 1.5 x the max log size in MB since each entry is 1440 // about 500kB (in memory) in size. 1.5x is probably good enough to ensure 1441 // this test doesn't flake. 1442 c.Assert(float64(len(actions)), jc.LessThan, 2.0*maxLogSize*1.5) 1443 c.Assert(float64(len(ops)), gc.Equals, float64(len(actions))/3.0) 1444 } 1445 1446 func (s *ActionPruningSuite) TestPruneOperationsByAge(c *gc.C) { 1447 clock := testclock.NewClock(time.Now()) 1448 err := s.State.SetClockForTesting(clock) 1449 c.Assert(err, jc.ErrorIsNil) 1450 application := s.Factory.MakeApplication(c, nil) 1451 unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 1452 1453 const numCurrentOperationEntries = 5 1454 const numExpiredOperationEntries = 5 1455 const tasksPerOperation = 3 1456 const ageOfExpired = 10 * time.Hour 1457 1458 state.PrimeOperations(c, clock.Now(), unit, numCurrentOperationEntries, tasksPerOperation) 1459 state.PrimeOperations(c, clock.Now().Add(-1*ageOfExpired), unit, numExpiredOperationEntries, tasksPerOperation) 1460 1461 actions, err := unit.Actions() 1462 c.Assert(err, jc.ErrorIsNil) 1463 c.Assert(actions, gc.HasLen, tasksPerOperation*(numCurrentOperationEntries+numExpiredOperationEntries)) 1464 ops, err := s.Model.AllOperations() 1465 c.Assert(err, jc.ErrorIsNil) 1466 c.Assert(ops, gc.HasLen, numCurrentOperationEntries+numExpiredOperationEntries) 1467 1468 var stop <-chan struct{} 1469 err = state.PruneOperations(stop, s.State, 1*time.Hour, 0) 1470 c.Assert(err, jc.ErrorIsNil) 1471 1472 actions, err = unit.Actions() 1473 c.Assert(err, jc.ErrorIsNil) 1474 ops, err = s.Model.AllOperations() 1475 c.Assert(err, jc.ErrorIsNil) 1476 1477 c.Log(actions) 1478 c.Assert(actions, gc.HasLen, tasksPerOperation*numCurrentOperationEntries) 1479 c.Assert(ops, gc.HasLen, numCurrentOperationEntries) 1480 } 1481 1482 // Pruner should not prune operations with age of epoch time since the epoch is a 1483 // special value denoting an incomplete operation. 1484 func (s *ActionPruningSuite) TestDoNotPruneIncompleteOperations(c *gc.C) { 1485 clock := testclock.NewClock(time.Now()) 1486 err := s.State.SetClockForTesting(clock) 1487 c.Assert(err, jc.ErrorIsNil) 1488 application := s.Factory.MakeApplication(c, nil) 1489 unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 1490 1491 // Completed times with the zero value are designated not complete 1492 const numZeroValueEntries = 5 1493 const tasksPerOperation = 3 1494 state.PrimeOperations(c, time.Time{}, unit, numZeroValueEntries, tasksPerOperation) 1495 1496 _, err = unit.Actions() 1497 c.Assert(err, jc.ErrorIsNil) 1498 _, err = s.Model.AllOperations() 1499 c.Assert(err, jc.ErrorIsNil) 1500 1501 var stop <-chan struct{} 1502 err = state.PruneOperations(stop, s.State, 1*time.Hour, 0) 1503 c.Assert(err, jc.ErrorIsNil) 1504 1505 actions, err := unit.Actions() 1506 c.Assert(err, jc.ErrorIsNil) 1507 ops, err := s.Model.AllOperations() 1508 c.Assert(err, jc.ErrorIsNil) 1509 1510 c.Assert(len(actions), gc.Equals, tasksPerOperation*numZeroValueEntries) 1511 c.Assert(len(ops), gc.Equals, numZeroValueEntries) 1512 } 1513 1514 func (s *ActionPruningSuite) TestPruneLegacyActions(c *gc.C) { 1515 clock := testclock.NewClock(time.Now()) 1516 err := s.State.SetClockForTesting(clock) 1517 c.Assert(err, jc.ErrorIsNil) 1518 application := s.Factory.MakeApplication(c, nil) 1519 unit := s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 1520 1521 const numCurrentOperationEntries = 5 1522 const numExpiredOperationEntries = 5 1523 const tasksPerOperation = 3 1524 const ageOfExpired = 10 * time.Hour 1525 1526 state.PrimeOperations(c, clock.Now(), unit, numCurrentOperationEntries, tasksPerOperation) 1527 state.PrimeOperations(c, clock.Now().Add(-1*ageOfExpired), unit, numExpiredOperationEntries, tasksPerOperation) 1528 state.PrimeLegacyActions(c, clock.Now().Add(-1*ageOfExpired), unit, numExpiredOperationEntries) 1529 1530 actions, err := unit.Actions() 1531 c.Assert(err, jc.ErrorIsNil) 1532 c.Assert(actions, gc.HasLen, tasksPerOperation*(numCurrentOperationEntries+numExpiredOperationEntries)+numExpiredOperationEntries) 1533 ops, err := s.Model.AllOperations() 1534 c.Assert(err, jc.ErrorIsNil) 1535 c.Assert(ops, gc.HasLen, numCurrentOperationEntries+numExpiredOperationEntries) 1536 1537 var stop <-chan struct{} 1538 err = state.PruneOperations(stop, s.State, 1*time.Hour, 0) 1539 c.Assert(err, jc.ErrorIsNil) 1540 1541 actions, err = unit.Actions() 1542 c.Assert(err, jc.ErrorIsNil) 1543 ops, err = s.Model.AllOperations() 1544 c.Assert(err, jc.ErrorIsNil) 1545 1546 c.Log(actions) 1547 c.Assert(actions, gc.HasLen, tasksPerOperation*numCurrentOperationEntries) 1548 c.Assert(ops, gc.HasLen, numCurrentOperationEntries) 1549 }