github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/state/taskrunner_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package state_test 21 22 import ( 23 "errors" 24 "fmt" 25 "sort" 26 "strconv" 27 "strings" 28 "sync" 29 "time" 30 31 . "gopkg.in/check.v1" 32 "gopkg.in/tomb.v2" 33 34 "github.com/snapcore/snapd/logger" 35 "github.com/snapcore/snapd/overlord/state" 36 ) 37 38 type taskRunnerSuite struct{} 39 40 var _ = Suite(&taskRunnerSuite{}) 41 42 type stateBackend struct { 43 mu sync.Mutex 44 ensureBefore time.Duration 45 ensureBeforeSeen chan<- bool 46 } 47 48 func (b *stateBackend) Checkpoint([]byte) error { return nil } 49 50 func (b *stateBackend) EnsureBefore(d time.Duration) { 51 b.mu.Lock() 52 if d < b.ensureBefore { 53 b.ensureBefore = d 54 } 55 b.mu.Unlock() 56 if b.ensureBeforeSeen != nil { 57 b.ensureBeforeSeen <- true 58 } 59 } 60 61 func (b *stateBackend) RequestRestart(t state.RestartType) {} 62 63 func ensureChange(c *C, r *state.TaskRunner, sb *stateBackend, chg *state.Change) { 64 for i := 0; i < 20; i++ { 65 sb.ensureBefore = time.Hour 66 r.Ensure() 67 r.Wait() 68 chg.State().Lock() 69 s := chg.Status() 70 chg.State().Unlock() 71 if s.Ready() { 72 return 73 } 74 if sb.ensureBefore > 0 { 75 break 76 } 77 } 78 var statuses []string 79 chg.State().Lock() 80 for _, t := range chg.Tasks() { 81 statuses = append(statuses, t.Summary()+":"+t.Status().String()) 82 } 83 chg.State().Unlock() 84 c.Fatalf("Change didn't reach final state without blocking: %s", strings.Join(statuses, " ")) 85 } 86 87 // The result field encodes the expected order in which the task 88 // handlers will be called, assuming the provided setup is in place. 89 // 90 // Setup options: 91 // <task>:was-<status> - set task status before calling ensure (must be sensible) 92 // <task>:(do|undo)-block - block handler until task tomb dies 93 // <task>:(do|undo)-retry - return from handler with with state.Retry 94 // <task>:(do|undo)-error - return from handler with an error 95 // <task>:...:1,2 - one of the above, and add task to lanes 1 and 2 96 // chg:abort - call abort on the change 97 // 98 // Task wait order: ( t11 | t12 ) => ( t21 ) => ( t31 | t32 ) 99 // 100 // Task t12 has no undo. 101 // 102 // Final task statuses are tested based on the resulting events list. 103 // 104 var sequenceTests = []struct{ setup, result string }{{ 105 setup: "", 106 result: "t11:do t12:do t21:do t31:do t32:do", 107 }, { 108 setup: "t11:was-done t12:was-doing", 109 result: "t12:do t21:do t31:do t32:do", 110 }, { 111 setup: "t11:was-done t12:was-doing chg:abort", 112 result: "t11:undo", 113 }, { 114 setup: "t12:do-retry", 115 result: "t11:do t12:do t12:do-retry t12:do t21:do t31:do t32:do", 116 }, { 117 setup: "t11:do-block t12:do-error", 118 result: "t11:do t11:do-block t12:do t12:do-error t11:do-unblock t11:undo", 119 }, { 120 setup: "t11:do-error t12:do-block", 121 result: "t11:do t11:do-error t12:do t12:do-block t12:do-unblock", 122 }, { 123 setup: "t11:do-block t11:do-retry t12:do-error", 124 result: "t11:do t11:do-block t12:do t12:do-error t11:do-unblock t11:do-retry t11:undo", 125 }, { 126 setup: "t11:do-error t12:do-block t12:do-retry", 127 result: "t11:do t11:do-error t12:do t12:do-block t12:do-unblock t12:do-retry", 128 }, { 129 setup: "t31:do-error t21:undo-error", 130 result: "t11:do t12:do t21:do t31:do t31:do-error t32:do t32:undo t21:undo t21:undo-error t11:undo", 131 }, { 132 setup: "t21:do-set-ready", 133 result: "t11:do t12:do t21:do t31:do t32:do", 134 }, { 135 setup: "t31:do-error t21:undo-set-ready", 136 result: "t11:do t12:do t21:do t31:do t31:do-error t32:do t32:undo t21:undo t11:undo", 137 }, { 138 setup: "t11:was-done:1 t12:was-done:2 t21:was-done:1,2 t31:was-done:1 t32:do-error:2", 139 result: "t31:undo t32:do t32:do-error t21:undo t11:undo", 140 }, { 141 setup: "t11:was-done:1 t12:was-done:2 t21:was-done:2 t31:was-done:2 t32:do-error:2", 142 result: "t31:undo t32:do t32:do-error t21:undo", 143 }} 144 145 func (ts *taskRunnerSuite) TestSequenceTests(c *C) { 146 sb := &stateBackend{} 147 st := state.New(sb) 148 r := state.NewTaskRunner(st) 149 defer r.Stop() 150 151 ch := make(chan string, 256) 152 fn := func(label string) state.HandlerFunc { 153 return func(task *state.Task, tomb *tomb.Tomb) error { 154 st.Lock() 155 defer st.Unlock() 156 ch <- task.Summary() + ":" + label 157 var isSet bool 158 if task.Get(label+"-block", &isSet) == nil && isSet { 159 ch <- task.Summary() + ":" + label + "-block" 160 st.Unlock() 161 <-tomb.Dying() 162 st.Lock() 163 ch <- task.Summary() + ":" + label + "-unblock" 164 } 165 if task.Get(label+"-retry", &isSet) == nil && isSet { 166 task.Set(label+"-retry", false) 167 ch <- task.Summary() + ":" + label + "-retry" 168 return &state.Retry{} 169 } 170 if task.Get(label+"-error", &isSet) == nil && isSet { 171 ch <- task.Summary() + ":" + label + "-error" 172 return errors.New("boom") 173 } 174 if task.Get(label+"-set-ready", &isSet) == nil && isSet { 175 switch task.Status() { 176 case state.DoingStatus: 177 task.SetStatus(state.DoneStatus) 178 case state.UndoingStatus: 179 task.SetStatus(state.UndoneStatus) 180 } 181 } 182 return nil 183 } 184 } 185 r.AddHandler("do", fn("do"), nil) 186 r.AddHandler("do-undo", fn("do"), fn("undo")) 187 188 past := time.Now().AddDate(-1, 0, 0) 189 for _, test := range sequenceTests { 190 st.Lock() 191 192 // Delete previous changes. 193 st.Prune(past, 1, 1, 1) 194 195 chg := st.NewChange("install", "...") 196 tasks := make(map[string]*state.Task) 197 for _, name := range strings.Fields("t11 t12 t21 t31 t32") { 198 if name == "t12" { 199 tasks[name] = st.NewTask("do", name) 200 } else { 201 tasks[name] = st.NewTask("do-undo", name) 202 } 203 chg.AddTask(tasks[name]) 204 } 205 tasks["t21"].WaitFor(tasks["t11"]) 206 tasks["t21"].WaitFor(tasks["t12"]) 207 tasks["t31"].WaitFor(tasks["t21"]) 208 tasks["t32"].WaitFor(tasks["t21"]) 209 st.Unlock() 210 211 c.Logf("-----") 212 c.Logf("Testing setup: %s", test.setup) 213 214 statuses := make(map[string]state.Status) 215 for s := state.DefaultStatus; s <= state.ErrorStatus; s++ { 216 statuses[strings.ToLower(s.String())] = s 217 } 218 219 // Reset and prepare initial task state. 220 st.Lock() 221 for _, t := range chg.Tasks() { 222 t.SetStatus(state.DefaultStatus) 223 t.Set("do-error", false) 224 t.Set("do-block", false) 225 t.Set("undo-error", false) 226 t.Set("undo-block", false) 227 } 228 for _, item := range strings.Fields(test.setup) { 229 parts := strings.Split(item, ":") 230 if parts[0] == "chg" && parts[1] == "abort" { 231 chg.Abort() 232 } else { 233 if strings.HasPrefix(parts[1], "was-") { 234 tasks[parts[0]].SetStatus(statuses[parts[1][4:]]) 235 } else { 236 tasks[parts[0]].Set(parts[1], true) 237 } 238 } 239 if len(parts) > 2 { 240 lanes := strings.Split(parts[2], ",") 241 for _, lane := range lanes { 242 n, err := strconv.Atoi(lane) 243 c.Assert(err, IsNil) 244 tasks[parts[0]].JoinLane(n) 245 } 246 } 247 } 248 st.Unlock() 249 250 // Run change until final. 251 ensureChange(c, r, sb, chg) 252 253 // Compute order of events observed. 254 var events []string 255 var done bool 256 for !done { 257 select { 258 case ev := <-ch: 259 events = append(events, ev) 260 // Make t11/t12 and t31/t32 always show up in the 261 // same order if they're next to each other. 262 for i := len(events) - 2; i >= 0; i-- { 263 prev := events[i] 264 next := events[i+1] 265 switch strings.Split(next, ":")[1] { 266 case "do-unblock", "undo-unblock": 267 default: 268 if prev[1] == next[1] && prev[2] > next[2] { 269 events[i], events[i+1] = next, prev 270 continue 271 } 272 } 273 break 274 } 275 default: 276 done = true 277 } 278 } 279 280 c.Logf("Expected result: %s", test.result) 281 c.Assert(strings.Join(events, " "), Equals, test.result, Commentf("setup: %s", test.setup)) 282 283 // Compute final expected status for tasks. 284 finalStatus := make(map[string]state.Status) 285 // ... default when no handler is called 286 for tname := range tasks { 287 finalStatus[tname] = state.HoldStatus 288 } 289 // ... overwrite based on relevant setup 290 for _, item := range strings.Fields(test.setup) { 291 parts := strings.Split(item, ":") 292 if parts[0] == "chg" && parts[1] == "abort" && strings.Contains(test.setup, "t12:was-doing") { 293 // t12 has no undo so must hold if asked to abort when was doing. 294 finalStatus["t12"] = state.HoldStatus 295 } 296 if !strings.HasPrefix(parts[1], "was-") { 297 continue 298 } 299 switch strings.TrimPrefix(parts[1], "was-") { 300 case "do", "doing", "done": 301 finalStatus[parts[0]] = state.DoneStatus 302 case "abort", "undo", "undoing", "undone": 303 if parts[0] == "t12" { 304 finalStatus[parts[0]] = state.DoneStatus // no undo for t12 305 } else { 306 finalStatus[parts[0]] = state.UndoneStatus 307 } 308 case "was-error": 309 finalStatus[parts[0]] = state.ErrorStatus 310 case "was-hold": 311 finalStatus[parts[0]] = state.ErrorStatus 312 } 313 } 314 // ... and overwrite based on events observed. 315 for _, ev := range events { 316 parts := strings.Split(ev, ":") 317 switch parts[1] { 318 case "do": 319 finalStatus[parts[0]] = state.DoneStatus 320 case "undo": 321 finalStatus[parts[0]] = state.UndoneStatus 322 case "do-error", "undo-error": 323 finalStatus[parts[0]] = state.ErrorStatus 324 case "do-retry": 325 if parts[0] == "t12" && finalStatus["t11"] == state.ErrorStatus { 326 // t12 has no undo so must hold if asked to abort on retry. 327 finalStatus["t12"] = state.HoldStatus 328 } 329 } 330 } 331 332 st.Lock() 333 var gotStatus, wantStatus []string 334 for _, task := range chg.Tasks() { 335 gotStatus = append(gotStatus, task.Summary()+":"+task.Status().String()) 336 wantStatus = append(wantStatus, task.Summary()+":"+finalStatus[task.Summary()].String()) 337 } 338 st.Unlock() 339 340 c.Logf("Expected statuses: %s", strings.Join(wantStatus, " ")) 341 comment := Commentf("calls: %s", test.result) 342 c.Assert(strings.Join(gotStatus, " "), Equals, strings.Join(wantStatus, " "), comment) 343 } 344 } 345 346 func (ts *taskRunnerSuite) TestExternalAbort(c *C) { 347 sb := &stateBackend{} 348 st := state.New(sb) 349 r := state.NewTaskRunner(st) 350 defer r.Stop() 351 352 ch := make(chan bool) 353 r.AddHandler("blocking", func(t *state.Task, tb *tomb.Tomb) error { 354 ch <- true 355 <-tb.Dying() 356 return nil 357 }, nil) 358 359 st.Lock() 360 chg := st.NewChange("install", "...") 361 t := st.NewTask("blocking", "...") 362 chg.AddTask(t) 363 st.Unlock() 364 365 r.Ensure() 366 <-ch 367 368 st.Lock() 369 chg.Abort() 370 st.Unlock() 371 372 // The Abort above must make Ensure kill the task, or this will never end. 373 ensureChange(c, r, sb, chg) 374 } 375 376 func (ts *taskRunnerSuite) TestStopHandlerJustFinishing(c *C) { 377 sb := &stateBackend{} 378 st := state.New(sb) 379 r := state.NewTaskRunner(st) 380 defer r.Stop() 381 382 ch := make(chan bool) 383 r.AddHandler("just-finish", func(t *state.Task, tb *tomb.Tomb) error { 384 ch <- true 385 <-tb.Dying() 386 // just ignore and actually finishes 387 return nil 388 }, nil) 389 390 st.Lock() 391 chg := st.NewChange("install", "...") 392 t := st.NewTask("just-finish", "...") 393 chg.AddTask(t) 394 st.Unlock() 395 396 r.Ensure() 397 <-ch 398 r.Stop() 399 400 st.Lock() 401 defer st.Unlock() 402 c.Check(t.Status(), Equals, state.DoneStatus) 403 c.Check(t.DoingTime(), Not(Equals), 0) 404 c.Check(t.UndoingTime(), Equals, time.Duration(0)) 405 } 406 407 func (ts *taskRunnerSuite) TestStopKinds(c *C) { 408 sb := &stateBackend{} 409 st := state.New(sb) 410 r := state.NewTaskRunner(st) 411 defer r.Stop() 412 413 ch1 := make(chan bool) 414 ch2 := make(chan bool) 415 r.AddHandler("just-finish1", func(t *state.Task, tb *tomb.Tomb) error { 416 ch1 <- true 417 <-tb.Dying() 418 // just ignore and actually finishes 419 return nil 420 }, nil) 421 r.AddHandler("just-finish2", func(t *state.Task, tb *tomb.Tomb) error { 422 ch2 <- true 423 <-tb.Dying() 424 // just ignore and actually finishes 425 return nil 426 }, nil) 427 428 st.Lock() 429 chg := st.NewChange("install", "...") 430 t1 := st.NewTask("just-finish1", "...") 431 t2 := st.NewTask("just-finish2", "...") 432 chg.AddTask(t1) 433 chg.AddTask(t2) 434 st.Unlock() 435 436 r.Ensure() 437 <-ch1 438 <-ch2 439 r.StopKinds("just-finish1") 440 441 st.Lock() 442 defer st.Unlock() 443 c.Check(t1.Status(), Equals, state.DoneStatus) 444 c.Check(t2.Status(), Equals, state.DoingStatus) 445 446 st.Unlock() 447 r.Stop() 448 st.Lock() 449 c.Check(t2.Status(), Equals, state.DoneStatus) 450 } 451 452 func (ts *taskRunnerSuite) TestErrorsOnStopAreRetried(c *C) { 453 sb := &stateBackend{} 454 st := state.New(sb) 455 r := state.NewTaskRunner(st) 456 defer r.Stop() 457 458 ch := make(chan bool) 459 r.AddHandler("error-on-stop", func(t *state.Task, tb *tomb.Tomb) error { 460 ch <- true 461 <-tb.Dying() 462 // error here could be due to cancellation, task will be retried 463 return errors.New("error at stop") 464 }, nil) 465 466 st.Lock() 467 chg := st.NewChange("install", "...") 468 t := st.NewTask("error-on-stop", "...") 469 chg.AddTask(t) 470 st.Unlock() 471 472 r.Ensure() 473 <-ch 474 r.Stop() 475 476 st.Lock() 477 defer st.Unlock() 478 // still Doing, will be retried 479 c.Check(t.Status(), Equals, state.DoingStatus) 480 } 481 482 func (ts *taskRunnerSuite) TestStopAskForRetry(c *C) { 483 sb := &stateBackend{} 484 st := state.New(sb) 485 r := state.NewTaskRunner(st) 486 defer r.Stop() 487 488 ch := make(chan bool) 489 r.AddHandler("ask-for-retry", func(t *state.Task, tb *tomb.Tomb) error { 490 ch <- true 491 <-tb.Dying() 492 // ask for retry 493 return &state.Retry{After: 2 * time.Minute} 494 }, nil) 495 496 st.Lock() 497 chg := st.NewChange("install", "...") 498 t := st.NewTask("ask-for-retry", "...") 499 chg.AddTask(t) 500 st.Unlock() 501 502 r.Ensure() 503 <-ch 504 r.Stop() 505 506 st.Lock() 507 defer st.Unlock() 508 c.Check(t.Status(), Equals, state.DoingStatus) 509 c.Check(t.AtTime().IsZero(), Equals, false) 510 } 511 512 func (ts *taskRunnerSuite) TestRetryAfterDuration(c *C) { 513 ensureBeforeTick := make(chan bool, 1) 514 sb := &stateBackend{ 515 ensureBefore: time.Hour, 516 ensureBeforeSeen: ensureBeforeTick, 517 } 518 st := state.New(sb) 519 r := state.NewTaskRunner(st) 520 defer r.Stop() 521 522 ch := make(chan bool) 523 ask := 0 524 r.AddHandler("ask-for-retry", func(t *state.Task, _ *tomb.Tomb) error { 525 ask++ 526 if ask == 1 { 527 return &state.Retry{After: time.Minute} 528 } 529 ch <- true 530 return nil 531 }, nil) 532 533 st.Lock() 534 chg := st.NewChange("install", "...") 535 t := st.NewTask("ask-for-retry", "...") 536 chg.AddTask(t) 537 st.Unlock() 538 539 tock := time.Now() 540 restore := state.MockTime(tock) 541 defer restore() 542 r.Ensure() // will run and be rescheduled in a minute 543 select { 544 case <-ensureBeforeTick: 545 case <-time.After(2 * time.Second): 546 c.Fatal("EnsureBefore wasn't called") 547 } 548 549 st.Lock() 550 defer st.Unlock() 551 c.Check(t.Status(), Equals, state.DoingStatus) 552 553 c.Check(ask, Equals, 1) 554 c.Check(sb.ensureBefore, Equals, 1*time.Minute) 555 schedule := t.AtTime() 556 c.Check(schedule.IsZero(), Equals, false) 557 558 state.MockTime(tock.Add(5 * time.Second)) 559 sb.ensureBefore = time.Hour 560 st.Unlock() 561 r.Ensure() // too soon 562 st.Lock() 563 564 c.Check(t.Status(), Equals, state.DoingStatus) 565 c.Check(ask, Equals, 1) 566 c.Check(sb.ensureBefore, Equals, 55*time.Second) 567 c.Check(t.AtTime().Equal(schedule), Equals, true) 568 569 state.MockTime(schedule) 570 sb.ensureBefore = time.Hour 571 st.Unlock() 572 r.Ensure() // time to run again 573 select { 574 case <-ch: 575 case <-time.After(2 * time.Second): 576 c.Fatal("handler wasn't called") 577 } 578 579 // wait for handler to finish 580 r.Wait() 581 582 st.Lock() 583 c.Check(t.Status(), Equals, state.DoneStatus) 584 c.Check(ask, Equals, 2) 585 c.Check(sb.ensureBefore, Equals, time.Hour) 586 c.Check(t.AtTime().IsZero(), Equals, true) 587 } 588 589 func (ts *taskRunnerSuite) testTaskSerialization(c *C, setupBlocked func(r *state.TaskRunner)) { 590 ensureBeforeTick := make(chan bool, 1) 591 sb := &stateBackend{ 592 ensureBefore: time.Hour, 593 ensureBeforeSeen: ensureBeforeTick, 594 } 595 st := state.New(sb) 596 r := state.NewTaskRunner(st) 597 defer r.Stop() 598 599 ch1 := make(chan bool) 600 ch2 := make(chan bool) 601 r.AddHandler("do1", func(t *state.Task, _ *tomb.Tomb) error { 602 ch1 <- true 603 ch1 <- true 604 return nil 605 }, nil) 606 r.AddHandler("do2", func(t *state.Task, _ *tomb.Tomb) error { 607 ch2 <- true 608 return nil 609 }, nil) 610 611 // setup blocked predicates 612 setupBlocked(r) 613 614 st.Lock() 615 chg := st.NewChange("install", "...") 616 t1 := st.NewTask("do1", "...") 617 chg.AddTask(t1) 618 t2 := st.NewTask("do2", "...") 619 chg.AddTask(t2) 620 st.Unlock() 621 622 r.Ensure() // will start only one, do1 623 624 select { 625 case <-ch1: 626 case <-time.After(2 * time.Second): 627 c.Fatal("do1 wasn't called") 628 } 629 630 c.Check(ensureBeforeTick, HasLen, 0) 631 c.Check(ch2, HasLen, 0) 632 633 r.Ensure() // won't yet start anything new 634 635 c.Check(ensureBeforeTick, HasLen, 0) 636 c.Check(ch2, HasLen, 0) 637 638 // finish do1 639 select { 640 case <-ch1: 641 case <-time.After(2 * time.Second): 642 c.Fatal("do1 wasn't continued") 643 } 644 645 // getting an EnsureBefore 0 call 646 select { 647 case <-ensureBeforeTick: 648 case <-time.After(2 * time.Second): 649 c.Fatal("EnsureBefore wasn't called") 650 } 651 c.Check(sb.ensureBefore, Equals, time.Duration(0)) 652 653 r.Ensure() // will start do2 654 655 select { 656 case <-ch2: 657 case <-time.After(2 * time.Second): 658 c.Fatal("do2 wasn't called") 659 } 660 661 // no more EnsureBefore calls 662 c.Check(ensureBeforeTick, HasLen, 0) 663 } 664 665 func (ts *taskRunnerSuite) TestTaskSerializationSetBlocked(c *C) { 666 // start first do1, and then do2 when nothing else is running 667 startedDo1 := false 668 ts.testTaskSerialization(c, func(r *state.TaskRunner) { 669 r.SetBlocked(func(t *state.Task, running []*state.Task) bool { 670 if t.Kind() == "do2" && (len(running) != 0 || !startedDo1) { 671 return true 672 } 673 if t.Kind() == "do1" { 674 startedDo1 = true 675 } 676 return false 677 }) 678 }) 679 } 680 681 func (ts *taskRunnerSuite) TestTaskSerializationAddBlocked(c *C) { 682 // start first do1, and then do2 when nothing else is running 683 startedDo1 := false 684 ts.testTaskSerialization(c, func(r *state.TaskRunner) { 685 r.AddBlocked(func(t *state.Task, running []*state.Task) bool { 686 if t.Kind() == "do2" && (len(running) != 0 || !startedDo1) { 687 return true 688 } 689 return false 690 }) 691 r.AddBlocked(func(t *state.Task, running []*state.Task) bool { 692 if t.Kind() == "do1" { 693 startedDo1 = true 694 } 695 return false 696 }) 697 }) 698 } 699 700 func (ts *taskRunnerSuite) TestPrematureChangeReady(c *C) { 701 sb := &stateBackend{} 702 st := state.New(sb) 703 r := state.NewTaskRunner(st) 704 defer r.Stop() 705 706 ch := make(chan bool) 707 r.AddHandler("block-undo", func(t *state.Task, tb *tomb.Tomb) error { return nil }, 708 func(t *state.Task, tb *tomb.Tomb) error { 709 ch <- true 710 <-ch 711 return nil 712 }) 713 r.AddHandler("fail", func(t *state.Task, tb *tomb.Tomb) error { 714 return errors.New("BAM") 715 }, nil) 716 717 st.Lock() 718 chg := st.NewChange("install", "...") 719 t1 := st.NewTask("block-undo", "...") 720 t2 := st.NewTask("fail", "...") 721 chg.AddTask(t1) 722 chg.AddTask(t2) 723 st.Unlock() 724 725 r.Ensure() // Error 726 r.Wait() 727 r.Ensure() // Block on undo 728 <-ch 729 730 defer func() { 731 ch <- true 732 r.Wait() 733 }() 734 735 st.Lock() 736 defer st.Unlock() 737 738 if chg.IsReady() || chg.Status().Ready() { 739 c.Errorf("Change considered ready prematurely") 740 } 741 742 c.Assert(chg.Err(), IsNil) 743 } 744 745 func (ts *taskRunnerSuite) TestOptionalHandler(c *C) { 746 sb := &stateBackend{} 747 st := state.New(sb) 748 r := state.NewTaskRunner(st) 749 750 r.AddOptionalHandler(func(t *state.Task) bool { return true }, 751 func(t *state.Task, tomb *tomb.Tomb) error { 752 return fmt.Errorf("optional handler error for %q", t.Kind()) 753 }, nil) 754 755 st.Lock() 756 chg := st.NewChange("install", "...") 757 t1 := st.NewTask("an unknown task", "...") 758 chg.AddTask(t1) 759 st.Unlock() 760 761 // Mark tasks as done. 762 ensureChange(c, r, sb, chg) 763 r.Stop() 764 765 st.Lock() 766 defer st.Unlock() 767 c.Assert(t1.Status(), Equals, state.ErrorStatus) 768 c.Assert(strings.Join(t1.Log(), ""), Matches, `.*optional handler error for "an unknown task"`) 769 } 770 771 func (ts *taskRunnerSuite) TestUndoSequence(c *C) { 772 sb := &stateBackend{} 773 st := state.New(sb) 774 r := state.NewTaskRunner(st) 775 776 var events []string 777 778 r.AddHandler("do-with-undo", 779 func(t *state.Task, tb *tomb.Tomb) error { 780 events = append(events, fmt.Sprintf("do-with-undo:%s", t.ID())) 781 return nil 782 }, func(t *state.Task, tb *tomb.Tomb) error { 783 events = append(events, fmt.Sprintf("undo:%s", t.ID())) 784 return nil 785 }) 786 r.AddHandler("do-no-undo", 787 func(t *state.Task, tb *tomb.Tomb) error { 788 events = append(events, fmt.Sprintf("do-no-undo:%s", t.ID())) 789 return nil 790 }, nil) 791 792 r.AddHandler("error-trigger", 793 func(t *state.Task, tb *tomb.Tomb) error { 794 events = append(events, fmt.Sprintf("do-with-error:%s", t.ID())) 795 return fmt.Errorf("error") 796 }, nil) 797 798 st.Lock() 799 chg := st.NewChange("install", "...") 800 801 var prev *state.Task 802 803 // create a sequence of tasks: 3 tasks with undo handlers, a task with 804 // no undo handler, 3 tasks with undo handler, a task with no undo 805 // handler, finally a task that errors out. Every task waits for previous 806 // taske. 807 for i := 0; i < 2; i++ { 808 for j := 0; j < 3; j++ { 809 t := st.NewTask("do-with-undo", "...") 810 if prev != nil { 811 t.WaitFor(prev) 812 } 813 chg.AddTask(t) 814 prev = t 815 } 816 817 t := st.NewTask("do-no-undo", "...") 818 t.WaitFor(prev) 819 chg.AddTask(t) 820 prev = t 821 } 822 823 terr := st.NewTask("error-trigger", "provoking undo") 824 terr.WaitFor(prev) 825 chg.AddTask(terr) 826 827 c.Check(chg.Tasks(), HasLen, 9) // sanity check 828 829 st.Unlock() 830 831 ensureChange(c, r, sb, chg) 832 r.Stop() 833 834 c.Assert(events, DeepEquals, []string{ 835 "do-with-undo:1", 836 "do-with-undo:2", 837 "do-with-undo:3", 838 "do-no-undo:4", 839 "do-with-undo:5", 840 "do-with-undo:6", 841 "do-with-undo:7", 842 "do-no-undo:8", 843 "do-with-error:9", 844 "undo:7", 845 "undo:6", 846 "undo:5", 847 "undo:3", 848 "undo:2", 849 "undo:1"}) 850 } 851 852 func (ts *taskRunnerSuite) TestKnownTaskKinds(c *C) { 853 st := state.New(nil) 854 r := state.NewTaskRunner(st) 855 r.AddHandler("task-kind-1", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil) 856 r.AddHandler("task-kind-2", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil) 857 858 kinds := r.KnownTaskKinds() 859 sort.Strings(kinds) 860 c.Assert(kinds, DeepEquals, []string{"task-kind-1", "task-kind-2"}) 861 } 862 863 func (ts *taskRunnerSuite) TestCleanup(c *C) { 864 sb := &stateBackend{} 865 st := state.New(sb) 866 r := state.NewTaskRunner(st) 867 defer r.Stop() 868 869 r.AddHandler("clean-it", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil) 870 r.AddHandler("other", func(t *state.Task, tb *tomb.Tomb) error { return nil }, nil) 871 872 called := 0 873 r.AddCleanup("clean-it", func(t *state.Task, tb *tomb.Tomb) error { 874 called++ 875 if called == 1 { 876 return fmt.Errorf("retry me") 877 } 878 return nil 879 }) 880 881 st.Lock() 882 chg := st.NewChange("install", "...") 883 t1 := st.NewTask("clean-it", "...") 884 t2 := st.NewTask("other", "...") 885 chg.AddTask(t1) 886 chg.AddTask(t2) 887 st.Unlock() 888 889 chgIsClean := func() bool { 890 st.Lock() 891 defer st.Unlock() 892 return chg.IsClean() 893 } 894 895 // Mark tasks as done. 896 ensureChange(c, r, sb, chg) 897 898 // First time it errors, then it works, then it's ignored. 899 c.Assert(chgIsClean(), Equals, false) 900 c.Assert(called, Equals, 0) 901 r.Ensure() 902 r.Wait() 903 c.Assert(chgIsClean(), Equals, false) 904 c.Assert(called, Equals, 1) 905 r.Ensure() 906 r.Wait() 907 c.Assert(chgIsClean(), Equals, true) 908 c.Assert(called, Equals, 2) 909 r.Ensure() 910 r.Wait() 911 c.Assert(chgIsClean(), Equals, true) 912 c.Assert(called, Equals, 2) 913 } 914 915 func (ts *taskRunnerSuite) TestErrorCallbackCalledOnError(c *C) { 916 logbuf, restore := logger.MockLogger() 917 defer restore() 918 919 sb := &stateBackend{} 920 st := state.New(sb) 921 r := state.NewTaskRunner(st) 922 923 var called bool 924 r.OnTaskError(func(err error) { 925 called = true 926 }) 927 928 r.AddHandler("foo", func(t *state.Task, tomb *tomb.Tomb) error { 929 return fmt.Errorf("handler error for %q", t.Kind()) 930 }, nil) 931 932 st.Lock() 933 chg := st.NewChange("install", "change summary") 934 t1 := st.NewTask("foo", "task summary") 935 chg.AddTask(t1) 936 st.Unlock() 937 938 // Mark tasks as done. 939 ensureChange(c, r, sb, chg) 940 r.Stop() 941 942 st.Lock() 943 defer st.Unlock() 944 945 c.Check(t1.Status(), Equals, state.ErrorStatus) 946 c.Check(strings.Join(t1.Log(), ""), Matches, `.*handler error for "foo"`) 947 c.Check(called, Equals, true) 948 949 c.Check(logbuf.String(), Matches, `(?m).*: \[change 1 "task summary" task\] failed: handler error for "foo".*`) 950 } 951 952 func (ts *taskRunnerSuite) TestErrorCallbackNotCalled(c *C) { 953 sb := &stateBackend{} 954 st := state.New(sb) 955 r := state.NewTaskRunner(st) 956 957 var called bool 958 r.OnTaskError(func(err error) { 959 called = true 960 }) 961 962 r.AddHandler("foo", func(t *state.Task, tomb *tomb.Tomb) error { 963 return nil 964 }, nil) 965 966 st.Lock() 967 chg := st.NewChange("install", "...") 968 t1 := st.NewTask("foo", "...") 969 chg.AddTask(t1) 970 st.Unlock() 971 972 // Mark tasks as done. 973 ensureChange(c, r, sb, chg) 974 r.Stop() 975 976 st.Lock() 977 defer st.Unlock() 978 979 c.Check(t1.Status(), Equals, state.DoneStatus) 980 c.Check(called, Equals, false) 981 }