github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/hex" 8 "fmt" 9 "strings" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/txn" 14 "github.com/juju/utils" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 18 "github.com/juju/juju/state" 19 statetesting "github.com/juju/juju/state/testing" 20 "github.com/juju/juju/testing" 21 ) 22 23 type ActionSuite struct { 24 ConnSuite 25 charm *state.Charm 26 actionlessCharm *state.Charm 27 service *state.Application 28 actionlessService *state.Application 29 unit *state.Unit 30 unit2 *state.Unit 31 charmlessUnit *state.Unit 32 actionlessUnit *state.Unit 33 } 34 35 var _ = gc.Suite(&ActionSuite{}) 36 37 func (s *ActionSuite) SetUpTest(c *gc.C) { 38 var err error 39 40 s.ConnSuite.SetUpTest(c) 41 42 s.charm = s.AddTestingCharm(c, "dummy") 43 s.actionlessCharm = s.AddTestingCharm(c, "actionless") 44 45 s.service = s.AddTestingService(c, "dummy", s.charm) 46 c.Assert(err, jc.ErrorIsNil) 47 s.actionlessService = s.AddTestingService(c, "actionless", s.actionlessCharm) 48 c.Assert(err, jc.ErrorIsNil) 49 50 sURL, _ := s.service.CharmURL() 51 c.Assert(sURL, gc.NotNil) 52 actionlessSURL, _ := s.actionlessService.CharmURL() 53 c.Assert(actionlessSURL, gc.NotNil) 54 55 s.unit, err = s.service.AddUnit() 56 c.Assert(err, jc.ErrorIsNil) 57 c.Assert(s.unit.Series(), gc.Equals, "quantal") 58 59 err = s.unit.SetCharmURL(sURL) 60 c.Assert(err, jc.ErrorIsNil) 61 62 s.unit2, err = s.service.AddUnit() 63 c.Assert(err, jc.ErrorIsNil) 64 c.Assert(s.unit2.Series(), gc.Equals, "quantal") 65 66 err = s.unit2.SetCharmURL(sURL) 67 c.Assert(err, jc.ErrorIsNil) 68 69 s.charmlessUnit, err = s.service.AddUnit() 70 c.Assert(err, jc.ErrorIsNil) 71 c.Assert(s.charmlessUnit.Series(), gc.Equals, "quantal") 72 73 s.actionlessUnit, err = s.actionlessService.AddUnit() 74 c.Assert(err, jc.ErrorIsNil) 75 c.Assert(s.actionlessUnit.Series(), gc.Equals, "quantal") 76 77 err = s.actionlessUnit.SetCharmURL(actionlessSURL) 78 c.Assert(err, jc.ErrorIsNil) 79 } 80 81 func (s *ActionSuite) TestActionTag(c *gc.C) { 82 action, err := s.unit.AddAction("snapshot", nil) 83 c.Assert(err, jc.ErrorIsNil) 84 85 tag := action.Tag() 86 c.Assert(tag.String(), gc.Equals, "action-"+action.Id()) 87 88 result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted}) 89 c.Assert(err, jc.ErrorIsNil) 90 91 actions, err := s.unit.CompletedActions() 92 c.Assert(err, jc.ErrorIsNil) 93 c.Assert(len(actions), gc.Equals, 1) 94 95 actionResult := actions[0] 96 c.Assert(actionResult, gc.DeepEquals, result) 97 98 tag = actionResult.Tag() 99 c.Assert(tag.String(), gc.Equals, "action-"+actionResult.Id()) 100 } 101 102 func (s *ActionSuite) TestAddAction(c *gc.C) { 103 for i, t := range []struct { 104 should string 105 name string 106 params map[string]interface{} 107 whichUnit *state.Unit 108 expectedErr string 109 }{{ 110 should: "enqueue normally", 111 name: "snapshot", 112 whichUnit: s.unit, 113 //params: map[string]interface{}{"outfile": "outfile.tar.bz2"}, 114 }, { 115 should: "fail on actionless charms", 116 name: "something", 117 whichUnit: s.actionlessUnit, 118 expectedErr: "no actions defined on charm \"local:quantal/quantal-actionless-1\"", 119 }, { 120 should: "fail on action not defined in schema", 121 whichUnit: s.unit, 122 name: "something-nonexistent", 123 expectedErr: "action \"something-nonexistent\" not defined on unit \"dummy/0\"", 124 }, { 125 should: "invalidate with bad params", 126 whichUnit: s.unit, 127 name: "snapshot", 128 params: map[string]interface{}{ 129 "outfile": 5.0, 130 }, 131 expectedErr: "validation failed: \\(root\\)\\.outfile : must be of type string, given 5", 132 }} { 133 c.Logf("Test %d: should %s", i, t.should) 134 before := s.State.NowToTheSecond() 135 later := before.Add(testing.LongWait) 136 137 // Copy params over into empty premade map for comparison later 138 params := make(map[string]interface{}) 139 for k, v := range t.params { 140 params[k] = v 141 } 142 143 // Verify we can add an Action 144 a, err := t.whichUnit.AddAction(t.name, params) 145 146 if t.expectedErr == "" { 147 c.Assert(err, jc.ErrorIsNil) 148 curl, _ := t.whichUnit.CharmURL() 149 ch, _ := s.State.Charm(curl) 150 schema := ch.Actions() 151 c.Logf("Schema for unit %q:\n%#v", t.whichUnit.Name(), schema) 152 // verify we can get it back out by Id 153 action, err := s.State.Action(a.Id()) 154 c.Assert(err, jc.ErrorIsNil) 155 c.Assert(action, gc.NotNil) 156 c.Check(action.Id(), gc.Equals, a.Id()) 157 158 // verify we get out what we put in 159 c.Check(action.Name(), gc.Equals, t.name) 160 c.Check(action.Parameters(), jc.DeepEquals, params) 161 162 // Enqueued time should be within a reasonable time of the beginning 163 // of the test 164 now := s.State.NowToTheSecond() 165 c.Check(action.Enqueued(), jc.TimeBetween(before, now)) 166 c.Check(action.Enqueued(), jc.TimeBetween(before, later)) 167 continue 168 } 169 170 c.Check(err, gc.ErrorMatches, t.expectedErr) 171 } 172 } 173 174 func (s *ActionSuite) TestAddActionInsertsDefaults(c *gc.C) { 175 units := make(map[string]*state.Unit) 176 schemas := map[string]string{ 177 "simple": ` 178 act: 179 params: 180 val: 181 type: string 182 default: somestr 183 `[1:], 184 "complicated": ` 185 act: 186 params: 187 val: 188 type: object 189 properties: 190 foo: 191 type: string 192 bar: 193 type: object 194 properties: 195 baz: 196 type: string 197 default: woz 198 `[1:], 199 "none": ` 200 act: 201 params: 202 val: 203 type: string 204 `[1:]} 205 206 // Prepare the units for this test 207 makeUnits(c, s, units, schemas) 208 209 for i, t := range []struct { 210 should string 211 params map[string]interface{} 212 schema string 213 expectedParams map[string]interface{} 214 }{{ 215 should: "do nothing with no defaults", 216 params: map[string]interface{}{}, 217 schema: "none", 218 expectedParams: map[string]interface{}{}, 219 }, { 220 should: "insert a simple default value", 221 params: map[string]interface{}{"foo": "bar"}, 222 schema: "simple", 223 expectedParams: map[string]interface{}{ 224 "foo": "bar", 225 "val": "somestr", 226 }, 227 }, { 228 should: "insert a default value when an empty map is passed", 229 params: map[string]interface{}{}, 230 schema: "simple", 231 expectedParams: map[string]interface{}{ 232 "val": "somestr", 233 }, 234 }, { 235 should: "insert a default value when a nil map is passed", 236 params: nil, 237 schema: "simple", 238 expectedParams: map[string]interface{}{ 239 "val": "somestr", 240 }, 241 }, { 242 should: "insert a nested default value", 243 params: map[string]interface{}{"foo": "bar"}, 244 schema: "complicated", 245 expectedParams: map[string]interface{}{ 246 "foo": "bar", 247 "val": map[string]interface{}{ 248 "bar": map[string]interface{}{ 249 "baz": "woz", 250 }}}, 251 }} { 252 c.Logf("test %d: should %s", i, t.should) 253 u := units[t.schema] 254 // Note that AddAction will only result in errors in the case 255 // of malformed schemas, and schema objects can only be 256 // created from valid schemas. The error handling for this 257 // is tested in the gojsonschema package. 258 action, err := u.AddAction("act", t.params) 259 c.Assert(err, jc.ErrorIsNil) 260 c.Check(action.Parameters(), jc.DeepEquals, t.expectedParams) 261 } 262 } 263 264 // makeUnits prepares units with given Action schemas 265 func makeUnits(c *gc.C, s *ActionSuite, units map[string]*state.Unit, schemas map[string]string) { 266 // A few dummy charms that haven't been used yet 267 freeCharms := map[string]string{ 268 "simple": "mysql", 269 "complicated": "mysql-alternative", 270 "none": "wordpress", 271 } 272 273 for name, schema := range schemas { 274 svcName := name + "-defaults-service" 275 276 // Add a testing service 277 ch := s.AddActionsCharm(c, freeCharms[name], schema, 1) 278 svc := s.AddTestingService(c, svcName, ch) 279 280 // Get its charm URL 281 sURL, _ := svc.CharmURL() 282 c.Assert(sURL, gc.NotNil) 283 284 // Add a unit 285 var err error 286 u, err := svc.AddUnit() 287 c.Assert(err, jc.ErrorIsNil) 288 c.Assert(u.Series(), gc.Equals, "quantal") 289 err = u.SetCharmURL(sURL) 290 c.Assert(err, jc.ErrorIsNil) 291 292 units[name] = u 293 } 294 } 295 296 func (s *ActionSuite) TestEnqueueActionRequiresName(c *gc.C) { 297 name := "" 298 299 // verify can not enqueue an Action without a name 300 _, err := s.State.EnqueueAction(s.unit.Tag(), name, nil) 301 c.Assert(err, gc.ErrorMatches, "action name required") 302 } 303 304 func (s *ActionSuite) TestAddActionAcceptsDuplicateNames(c *gc.C) { 305 name := "snapshot" 306 params1 := map[string]interface{}{"outfile": "outfile.tar.bz2"} 307 params2 := map[string]interface{}{"infile": "infile.zip"} 308 309 // verify can add two actions with same name 310 a1, err := s.unit.AddAction(name, params1) 311 c.Assert(err, jc.ErrorIsNil) 312 313 a2, err := s.unit.AddAction(name, params2) 314 c.Assert(err, jc.ErrorIsNil) 315 316 c.Assert(a1.Id(), gc.Not(gc.Equals), a2.Id()) 317 318 // verify both actually got added 319 actions, err := s.unit.PendingActions() 320 c.Assert(err, jc.ErrorIsNil) 321 c.Assert(len(actions), gc.Equals, 2) 322 323 // verify we can Fail one, retrieve the other, and they're not mixed up 324 action1, err := s.State.Action(a1.Id()) 325 c.Assert(err, jc.ErrorIsNil) 326 _, err = action1.Finish(state.ActionResults{Status: state.ActionFailed}) 327 c.Assert(err, jc.ErrorIsNil) 328 329 action2, err := s.State.Action(a2.Id()) 330 c.Assert(err, jc.ErrorIsNil) 331 c.Assert(action2.Parameters(), jc.DeepEquals, params2) 332 333 // verify only one left, and it's the expected one 334 actions, err = s.unit.PendingActions() 335 c.Assert(err, jc.ErrorIsNil) 336 c.Assert(len(actions), gc.Equals, 1) 337 c.Assert(actions[0].Id(), gc.Equals, a2.Id()) 338 } 339 340 func (s *ActionSuite) TestAddActionLifecycle(c *gc.C) { 341 unit, err := s.State.Unit(s.unit.Name()) 342 c.Assert(err, jc.ErrorIsNil) 343 preventUnitDestroyRemove(c, unit) 344 345 // make unit state Dying 346 err = unit.Destroy() 347 c.Assert(err, jc.ErrorIsNil) 348 349 // can add action to a dying unit 350 _, err = unit.AddAction("snapshot", map[string]interface{}{}) 351 c.Assert(err, jc.ErrorIsNil) 352 353 // make sure unit is dead 354 err = unit.EnsureDead() 355 c.Assert(err, jc.ErrorIsNil) 356 357 // cannot add action to a dead unit 358 _, err = unit.AddAction("snapshot", map[string]interface{}{}) 359 c.Assert(err, gc.Equals, state.ErrDead) 360 } 361 362 func (s *ActionSuite) TestAddActionFailsOnDeadUnitInTransaction(c *gc.C) { 363 unit, err := s.State.Unit(s.unit.Name()) 364 c.Assert(err, jc.ErrorIsNil) 365 preventUnitDestroyRemove(c, unit) 366 367 killUnit := txn.TestHook{ 368 Before: func() { 369 c.Assert(unit.Destroy(), gc.IsNil) 370 c.Assert(unit.EnsureDead(), gc.IsNil) 371 }, 372 } 373 defer state.SetTestHooks(c, s.State, killUnit).Check() 374 375 _, err = unit.AddAction("snapshot", map[string]interface{}{}) 376 c.Assert(err, gc.Equals, state.ErrDead) 377 } 378 379 func (s *ActionSuite) TestFail(c *gc.C) { 380 // get unit, add an action, retrieve that action 381 unit, err := s.State.Unit(s.unit.Name()) 382 c.Assert(err, jc.ErrorIsNil) 383 preventUnitDestroyRemove(c, unit) 384 385 a, err := unit.AddAction("snapshot", nil) 386 c.Assert(err, jc.ErrorIsNil) 387 388 action, err := s.State.Action(a.Id()) 389 c.Assert(err, jc.ErrorIsNil) 390 391 // ensure no action results for this action 392 results, err := unit.CompletedActions() 393 c.Assert(err, jc.ErrorIsNil) 394 c.Assert(len(results), gc.Equals, 0) 395 396 // fail the action, and verify that it succeeds 397 reason := "test fail reason" 398 result, err := action.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason}) 399 c.Assert(err, jc.ErrorIsNil) 400 401 // ensure we now have a result for this action 402 results, err = unit.CompletedActions() 403 c.Assert(err, jc.ErrorIsNil) 404 c.Assert(len(results), gc.Equals, 1) 405 c.Assert(results[0], gc.DeepEquals, result) 406 407 c.Assert(results[0].Name(), gc.Equals, action.Name()) 408 c.Assert(results[0].Status(), gc.Equals, state.ActionFailed) 409 410 // Verify the Action Completed time was within a reasonable 411 // time of the Enqueued time. 412 diff := results[0].Completed().Sub(action.Enqueued()) 413 c.Assert(diff >= 0, jc.IsTrue) 414 c.Assert(diff < testing.LongWait, jc.IsTrue) 415 416 res, errstr := results[0].Results() 417 c.Assert(errstr, gc.Equals, reason) 418 c.Assert(res, gc.DeepEquals, map[string]interface{}{}) 419 420 // validate that a pending action is no longer returned by UnitActions. 421 actions, err := unit.PendingActions() 422 c.Assert(err, jc.ErrorIsNil) 423 c.Assert(len(actions), gc.Equals, 0) 424 } 425 426 func (s *ActionSuite) TestComplete(c *gc.C) { 427 // get unit, add an action, retrieve that action 428 unit, err := s.State.Unit(s.unit.Name()) 429 c.Assert(err, jc.ErrorIsNil) 430 preventUnitDestroyRemove(c, unit) 431 432 a, err := unit.AddAction("snapshot", nil) 433 c.Assert(err, jc.ErrorIsNil) 434 435 action, err := s.State.Action(a.Id()) 436 c.Assert(err, jc.ErrorIsNil) 437 438 // ensure no action results for this action 439 results, err := unit.CompletedActions() 440 c.Assert(err, jc.ErrorIsNil) 441 c.Assert(len(results), gc.Equals, 0) 442 443 // complete the action, and verify that it succeeds 444 output := map[string]interface{}{"output": "action ran successfully"} 445 result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output}) 446 c.Assert(err, jc.ErrorIsNil) 447 448 // ensure we now have a result for this action 449 results, err = unit.CompletedActions() 450 c.Assert(err, jc.ErrorIsNil) 451 c.Assert(len(results), gc.Equals, 1) 452 c.Assert(results[0], gc.DeepEquals, result) 453 454 c.Assert(results[0].Name(), gc.Equals, action.Name()) 455 c.Assert(results[0].Status(), gc.Equals, state.ActionCompleted) 456 res, errstr := results[0].Results() 457 c.Assert(errstr, gc.Equals, "") 458 c.Assert(res, gc.DeepEquals, output) 459 460 // validate that a pending action is no longer returned by UnitActions. 461 actions, err := unit.PendingActions() 462 c.Assert(err, jc.ErrorIsNil) 463 c.Assert(len(actions), gc.Equals, 0) 464 } 465 466 func (s *ActionSuite) TestFindActionTagsByPrefix(c *gc.C) { 467 prefix := "feedbeef" 468 uuidMock := uuidMockHelper{} 469 uuidMock.SetPrefixMask(prefix) 470 s.PatchValue(&state.NewUUID, uuidMock.NewUUID) 471 472 actions := []struct { 473 Name string 474 Parameters map[string]interface{} 475 }{ 476 {Name: "action-1", Parameters: map[string]interface{}{}}, 477 {Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}}, 478 {Name: "action-9", Parameters: map[string]interface{}{"district": 9}}, 479 {Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}}, 480 } 481 482 for _, action := range actions { 483 _, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters) 484 c.Assert(err, gc.Equals, nil) 485 } 486 487 tags := s.State.FindActionTagsByPrefix(prefix) 488 489 c.Assert(len(tags), gc.Equals, len(actions)) 490 for i, tag := range tags { 491 c.Logf("check %q against %d:%q", prefix, i, tag) 492 c.Check(tag.Id()[:len(prefix)], gc.Equals, prefix) 493 } 494 } 495 496 func (s *ActionSuite) TestFindActionsByName(c *gc.C) { 497 actions := []struct { 498 Name string 499 Parameters map[string]interface{} 500 }{ 501 {Name: "action-1", Parameters: map[string]interface{}{}}, 502 {Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}}, 503 {Name: "action-1", Parameters: map[string]interface{}{"yeah": true, "take": nil}}, 504 {Name: "action-9", Parameters: map[string]interface{}{"district": 9}}, 505 {Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}}, 506 } 507 508 for _, action := range actions { 509 _, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters) 510 c.Assert(err, gc.Equals, nil) 511 } 512 513 results, err := s.State.FindActionsByName("action-1") 514 c.Assert(err, jc.ErrorIsNil) 515 516 c.Assert(len(results), gc.Equals, 2) 517 for _, result := range results { 518 c.Check(result.Name(), gc.Equals, "action-1") 519 } 520 } 521 522 func (s *ActionSuite) TestActionsWatcherEmitsInitialChanges(c *gc.C) { 523 // LP-1391914 :: idPrefixWatcher fails watcher contract to send 524 // initial Change event 525 // 526 // state/idPrefixWatcher does not send an initial event in response 527 // to the first time Changes() is called if all of the pending 528 // events are removed before the first consumption of Changes(). 529 // The watcher contract specifies that the first call to Changes() 530 // should always return at a minimum an empty change set to notify 531 // clients of it's initial state 532 533 // preamble 534 svc := s.AddTestingService(c, "dummy3", s.charm) 535 unit, err := svc.AddUnit() 536 c.Assert(err, jc.ErrorIsNil) 537 u, err := s.State.Unit(unit.Name()) 538 c.Assert(err, jc.ErrorIsNil) 539 preventUnitDestroyRemove(c, u) 540 541 // queue up actions 542 a1, err := u.AddAction("snapshot", nil) 543 c.Assert(err, jc.ErrorIsNil) 544 a2, err := u.AddAction("snapshot", nil) 545 c.Assert(err, jc.ErrorIsNil) 546 547 // start watcher but don't consume Changes() yet 548 w := u.WatchActionNotifications() 549 defer statetesting.AssertStop(c, w) 550 wc := statetesting.NewStringsWatcherC(c, s.State, w) 551 552 // remove actions 553 reason := "removed" 554 _, err = a1.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason}) 555 c.Assert(err, jc.ErrorIsNil) 556 _, err = a2.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason}) 557 c.Assert(err, jc.ErrorIsNil) 558 559 // per contract, there should be at minimum an initial empty Change() result 560 wc.AssertChangeMaybeIncluding(expectActionIds(a1, a2)...) 561 wc.AssertNoChange() 562 } 563 564 func (s *ActionSuite) TestUnitWatchActionNotifications(c *gc.C) { 565 // get units 566 unit1, err := s.State.Unit(s.unit.Name()) 567 c.Assert(err, jc.ErrorIsNil) 568 preventUnitDestroyRemove(c, unit1) 569 570 unit2, err := s.State.Unit(s.unit2.Name()) 571 c.Assert(err, jc.ErrorIsNil) 572 preventUnitDestroyRemove(c, unit2) 573 574 // queue some actions before starting the watcher 575 fa1, err := unit1.AddAction("snapshot", nil) 576 c.Assert(err, jc.ErrorIsNil) 577 fa2, err := unit1.AddAction("snapshot", nil) 578 c.Assert(err, jc.ErrorIsNil) 579 580 // set up watcher on first unit 581 w := unit1.WatchActionNotifications() 582 defer statetesting.AssertStop(c, w) 583 wc := statetesting.NewStringsWatcherC(c, s.State, w) 584 // make sure the previously pending actions are sent on the watcher 585 expect := expectActionIds(fa1, fa2) 586 wc.AssertChange(expect...) 587 wc.AssertNoChange() 588 589 // add watcher on unit2 590 w2 := unit2.WatchActionNotifications() 591 defer statetesting.AssertStop(c, w2) 592 wc2 := statetesting.NewStringsWatcherC(c, s.State, w2) 593 wc2.AssertChange() 594 wc2.AssertNoChange() 595 596 // add action on unit2 and makes sure unit1 watcher doesn't trigger 597 // and unit2 watcher does 598 fa3, err := unit2.AddAction("snapshot", nil) 599 c.Assert(err, jc.ErrorIsNil) 600 wc.AssertNoChange() 601 expect2 := expectActionIds(fa3) 602 wc2.AssertChange(expect2...) 603 wc2.AssertNoChange() 604 605 // add a couple actions on unit1 and make sure watcher sees events 606 fa4, err := unit1.AddAction("snapshot", nil) 607 c.Assert(err, jc.ErrorIsNil) 608 fa5, err := unit1.AddAction("snapshot", nil) 609 c.Assert(err, jc.ErrorIsNil) 610 611 expect = expectActionIds(fa4, fa5) 612 wc.AssertChange(expect...) 613 wc.AssertNoChange() 614 } 615 616 func (s *ActionSuite) TestMergeIds(c *gc.C) { 617 var tests = []struct { 618 changes string 619 adds string 620 removes string 621 expected string 622 }{ 623 {changes: "", adds: "a0,a1", removes: "", expected: "a0,a1"}, 624 {changes: "a0,a1", adds: "", removes: "a0", expected: "a1"}, 625 {changes: "a0,a1", adds: "a2", removes: "a0", expected: "a1,a2"}, 626 627 {changes: "", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 628 {changes: "", adds: "a0,a1,a2", removes: "a0,a1,a2", expected: ""}, 629 630 {changes: "a0", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 631 {changes: "a1", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 632 {changes: "a2", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"}, 633 634 {changes: "a3,a4", adds: "a1,a4,a5", removes: "a1,a3", expected: "a4,a5"}, 635 {changes: "a0,a1,a2", adds: "a1,a4,a5", removes: "a1,a3", expected: "a0,a2,a4,a5"}, 636 } 637 638 prefix := state.DocID(s.State, "") 639 640 for ix, test := range tests { 641 updates := mapify(prefix, test.adds, test.removes) 642 changes := sliceify("", test.changes) 643 expected := sliceify("", test.expected) 644 645 c.Log(fmt.Sprintf("test number %d %#v", ix, test)) 646 err := state.WatcherMergeIds(s.State, &changes, updates, state.ActionNotificationIdToActionId) 647 c.Assert(err, jc.ErrorIsNil) 648 c.Assert(changes, jc.SameContents, expected) 649 } 650 } 651 652 func (s *ActionSuite) TestMergeIdsErrors(c *gc.C) { 653 654 var tests = []struct { 655 name string 656 key interface{} 657 }{ 658 {name: "bool", key: true}, 659 {name: "int", key: 0}, 660 {name: "chan string", key: make(chan string)}, 661 } 662 663 for _, test := range tests { 664 changes, updates := []string{}, map[interface{}]bool{} 665 updates[test.key] = true 666 err := state.WatcherMergeIds(s.State, &changes, updates, state.ActionNotificationIdToActionId) 667 c.Assert(err, gc.ErrorMatches, "id is not of type string, got "+test.name) 668 } 669 } 670 671 func (s *ActionSuite) TestEnsureSuffix(c *gc.C) { 672 marker := "-marker-" 673 fn := state.WatcherEnsureSuffixFn(marker) 674 c.Assert(fn, gc.Not(gc.IsNil)) 675 676 var tests = []struct { 677 given string 678 expect string 679 }{ 680 {given: marker, expect: marker}, 681 {given: "", expect: "" + marker}, 682 {given: "asdf", expect: "asdf" + marker}, 683 {given: "asdf" + marker, expect: "asdf" + marker}, 684 {given: "asdf" + marker + "qwerty", expect: "asdf" + marker + "qwerty" + marker}, 685 } 686 687 for _, test := range tests { 688 c.Assert(fn(test.given), gc.Equals, test.expect) 689 } 690 } 691 692 func (s *ActionSuite) TestMakeIdFilter(c *gc.C) { 693 marker := "-marker-" 694 badmarker := "-bad-" 695 fn := state.WatcherMakeIdFilter(s.State, marker) 696 c.Assert(fn, gc.IsNil) 697 698 ar1 := mockAR{id: "mock/1"} 699 ar2 := mockAR{id: "mock/2"} 700 fn = state.WatcherMakeIdFilter(s.State, marker, ar1, ar2) 701 c.Assert(fn, gc.Not(gc.IsNil)) 702 703 var tests = []struct { 704 id string 705 match bool 706 }{ 707 {id: "mock/1" + marker + "", match: true}, 708 {id: "mock/1" + marker + "asdf", match: true}, 709 {id: "mock/2" + marker + "", match: true}, 710 {id: "mock/2" + marker + "asdf", match: true}, 711 712 {id: "mock/1" + badmarker + "", match: false}, 713 {id: "mock/1" + badmarker + "asdf", match: false}, 714 {id: "mock/2" + badmarker + "", match: false}, 715 {id: "mock/2" + badmarker + "asdf", match: false}, 716 717 {id: "mock/1" + marker + "0", match: true}, 718 {id: "mock/10" + marker + "0", match: false}, 719 {id: "mock/2" + marker + "0", match: true}, 720 {id: "mock/20" + marker + "0", match: false}, 721 {id: "mock" + marker + "0", match: false}, 722 723 {id: "" + marker + "0", match: false}, 724 {id: "mock/1-0", match: false}, 725 {id: "mock/1-0", match: false}, 726 } 727 728 for _, test := range tests { 729 c.Assert(fn(state.DocID(s.State, test.id)), gc.Equals, test.match) 730 } 731 } 732 733 func (s *ActionSuite) TestWatchActionNotifications(c *gc.C) { 734 svc := s.AddTestingService(c, "dummy2", s.charm) 735 u, err := svc.AddUnit() 736 c.Assert(err, jc.ErrorIsNil) 737 738 w := u.WatchActionNotifications() 739 defer statetesting.AssertStop(c, w) 740 wc := statetesting.NewStringsWatcherC(c, s.State, w) 741 wc.AssertChange() 742 wc.AssertNoChange() 743 744 // add 3 actions 745 fa1, err := u.AddAction("snapshot", nil) 746 c.Assert(err, jc.ErrorIsNil) 747 fa2, err := u.AddAction("snapshot", nil) 748 c.Assert(err, jc.ErrorIsNil) 749 fa3, err := u.AddAction("snapshot", nil) 750 c.Assert(err, jc.ErrorIsNil) 751 752 // fail the middle one 753 action, err := s.State.Action(fa2.Id()) 754 c.Assert(err, jc.ErrorIsNil) 755 _, err = action.Finish(state.ActionResults{Status: state.ActionFailed, Message: "die scum"}) 756 c.Assert(err, jc.ErrorIsNil) 757 758 // expect the first and last one in the watcher 759 expect := expectActionIds(fa1, fa3) 760 wc.AssertChange(expect...) 761 wc.AssertNoChange() 762 } 763 764 func (s *ActionSuite) TestActionStatusWatcher(c *gc.C) { 765 testCase := []struct { 766 receiver state.ActionReceiver 767 name string 768 status state.ActionStatus 769 }{ 770 {s.unit, "snapshot", state.ActionCancelled}, 771 {s.unit2, "snapshot", state.ActionCancelled}, 772 {s.unit, "snapshot", state.ActionPending}, 773 {s.unit2, "snapshot", state.ActionPending}, 774 {s.unit, "snapshot", state.ActionFailed}, 775 {s.unit2, "snapshot", state.ActionFailed}, 776 {s.unit, "snapshot", state.ActionCompleted}, 777 {s.unit2, "snapshot", state.ActionCompleted}, 778 } 779 780 w1 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}) 781 defer statetesting.AssertStop(c, w1) 782 783 w2 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionFailed) 784 defer statetesting.AssertStop(c, w2) 785 786 w3 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionCancelled, state.ActionCompleted) 787 defer statetesting.AssertStop(c, w3) 788 789 watchAny := statetesting.NewStringsWatcherC(c, s.State, w1) 790 watchAny.AssertChange() 791 watchAny.AssertNoChange() 792 793 watchFailed := statetesting.NewStringsWatcherC(c, s.State, w2) 794 watchFailed.AssertChange() 795 watchFailed.AssertNoChange() 796 797 watchCancelledOrCompleted := statetesting.NewStringsWatcherC(c, s.State, w3) 798 watchCancelledOrCompleted.AssertChange() 799 watchCancelledOrCompleted.AssertNoChange() 800 801 expect := map[state.ActionStatus][]state.Action{} 802 all := []state.Action{} 803 for _, tcase := range testCase { 804 a, err := tcase.receiver.AddAction(tcase.name, nil) 805 c.Assert(err, jc.ErrorIsNil) 806 807 action, err := s.State.Action(a.Id()) 808 c.Assert(err, jc.ErrorIsNil) 809 810 _, err = action.Finish(state.ActionResults{Status: tcase.status}) 811 c.Assert(err, jc.ErrorIsNil) 812 813 if tcase.receiver == s.unit { 814 expect[tcase.status] = append(expect[tcase.status], action) 815 all = append(all, action) 816 } 817 } 818 819 watchAny.AssertChange(expectActionIds(all...)...) 820 watchAny.AssertNoChange() 821 822 watchFailed.AssertChange(expectActionIds(expect[state.ActionFailed]...)...) 823 watchFailed.AssertNoChange() 824 825 cancelledAndCompleted := expectActionIds(append(expect[state.ActionCancelled], expect[state.ActionCompleted]...)...) 826 watchCancelledOrCompleted.AssertChange(cancelledAndCompleted...) 827 watchCancelledOrCompleted.AssertNoChange() 828 } 829 830 func expectActionIds(actions ...state.Action) []string { 831 ids := make([]string, len(actions)) 832 for i, action := range actions { 833 ids[i] = action.Id() 834 } 835 return ids 836 } 837 838 // mapify is a convenience method, also to make reading the tests 839 // easier. It combines two comma delimited strings representing 840 // additions and removals and turns it into the map[interface{}]bool 841 // format needed 842 func mapify(prefix, adds, removes string) map[interface{}]bool { 843 m := map[interface{}]bool{} 844 for _, v := range sliceify(prefix, adds) { 845 m[v] = true 846 } 847 for _, v := range sliceify(prefix, removes) { 848 m[v] = false 849 } 850 return m 851 } 852 853 // sliceify turns a comma separated list of strings into a slice 854 // trimming white space and excluding empty strings. 855 func sliceify(prefix, csvlist string) []string { 856 slice := []string{} 857 if csvlist == "" { 858 return slice 859 } 860 for _, entry := range strings.Split(csvlist, ",") { 861 clean := strings.TrimSpace(entry) 862 if clean != "" { 863 slice = append(slice, prefix+clean) 864 } 865 } 866 return slice 867 } 868 869 // mockAR is an implementation of ActionReceiver that can be used for 870 // testing that requires the ActionReceiver.Tag() call to return a 871 // names.Tag 872 type mockAR struct { 873 id string 874 } 875 876 var _ state.ActionReceiver = (*mockAR)(nil) 877 878 func (r mockAR) AddAction(name string, payload map[string]interface{}) (state.Action, error) { 879 return nil, nil 880 } 881 func (r mockAR) CancelAction(state.Action) (state.Action, error) { return nil, nil } 882 func (r mockAR) WatchActionNotifications() state.StringsWatcher { return nil } 883 func (r mockAR) Actions() ([]state.Action, error) { return nil, nil } 884 func (r mockAR) CompletedActions() ([]state.Action, error) { return nil, nil } 885 func (r mockAR) PendingActions() ([]state.Action, error) { return nil, nil } 886 func (r mockAR) RunningActions() ([]state.Action, error) { return nil, nil } 887 func (r mockAR) Tag() names.Tag { return names.NewUnitTag(r.id) } 888 889 // TestMock verifies the mock UUID generator works as expected. 890 func (s *ActionSuite) TestMock(c *gc.C) { 891 prefix := "abbadead" 892 uuidMock := uuidMockHelper{} 893 uuidMock.SetPrefixMask(prefix) 894 s.PatchValue(&state.NewUUID, uuidMock.NewUUID) 895 for i := 0; i < 10; i++ { 896 uuid, err := state.NewUUID() 897 c.Check(err, jc.ErrorIsNil) 898 c.Check(uuid.String()[:len(prefix)], gc.Equals, prefix) 899 } 900 } 901 902 type uuidGenFn func() (utils.UUID, error) 903 type uuidMockHelper struct { 904 original uuidGenFn 905 prefixMask []byte 906 } 907 908 func (h *uuidMockHelper) SetPrefixMask(prefix string) error { 909 prefix = strings.Replace(prefix, "-", "", 4) 910 mask, err := hex.DecodeString(prefix) 911 if err != nil { 912 return err 913 } 914 if len(mask) > 16 { 915 return errors.Errorf("prefix mask longer than uuid %q", prefix) 916 } 917 h.prefixMask = mask 918 return nil 919 } 920 921 func (h *uuidMockHelper) NewUUID() (utils.UUID, error) { 922 uuidGenFn := h.original 923 if uuidGenFn == nil { 924 uuidGenFn = utils.NewUUID 925 } 926 uuid, err := uuidGenFn() 927 if err != nil { 928 return uuid, errors.Trace(err) 929 } 930 return h.mask(uuid), nil 931 } 932 933 func (h *uuidMockHelper) mask(uuid utils.UUID) utils.UUID { 934 if len(h.prefixMask) > 0 { 935 for i, b := range h.prefixMask { 936 uuid[i] = b 937 } 938 } 939 return uuid 940 }