func (ts *taskRunnerSuite) TestExternalAbort(c *C) { sb := &stateBackend{} st := state.New(sb) r := state.NewTaskRunner(st) defer r.Stop() ch := make(chan bool) r.AddHandler("blocking", func(t *state.Task, tb *tomb.Tomb) error { ch <- true <-tb.Dying() return nil }, nil) st.Lock() chg := st.NewChange("install", "...") t := st.NewTask("blocking", "...") chg.AddTask(t) st.Unlock() r.Ensure() <-ch st.Lock() chg.Abort() st.Unlock() // The Abort above must make Ensure kill the task, or this will never end. ensureChange(c, r, sb, chg) }
func newRunnerManager(s *state.State) *runnerManager { rm := &runnerManager{ runner: state.NewTaskRunner(s), } rm.runner.AddHandler("runMgr1", func(t *state.Task, _ *tomb.Tomb) error { s := t.State() s.Lock() defer s.Unlock() s.Set("runMgr1Mark", 1) return nil }, nil) rm.runner.AddHandler("runMgr2", func(t *state.Task, _ *tomb.Tomb) error { s := t.State() s.Lock() defer s.Unlock() s.Set("runMgr2Mark", 1) return nil }, nil) rm.runner.AddHandler("runMgrEnsureBefore", func(t *state.Task, _ *tomb.Tomb) error { s := t.State() s.Lock() defer s.Unlock() s.EnsureBefore(20 * time.Millisecond) return nil }, nil) return rm }
// Manager returns a new InterfaceManager. // Extra interfaces can be provided for testing. func Manager(s *state.State, extra []interfaces.Interface) (*InterfaceManager, error) { runner := state.NewTaskRunner(s) m := &InterfaceManager{ state: s, runner: runner, repo: interfaces.NewRepository(), } if err := m.initialize(extra); err != nil { return nil, err } runner.AddHandler("connect", m.doConnect, nil) runner.AddHandler("disconnect", m.doDisconnect, nil) runner.AddHandler("setup-profiles", m.doSetupProfiles, m.doRemoveProfiles) runner.AddHandler("remove-profiles", m.doRemoveProfiles, m.doSetupProfiles) runner.AddHandler("discard-conns", m.doDiscardConns, m.undoDiscardConns) return m, nil }
// Manager returns a new snap manager. func Manager(s *state.State) (*SnapManager, error) { runner := state.NewTaskRunner(s) backend := &defaultBackend{} m := &SnapManager{ state: s, backend: backend, runner: runner, } // this handler does nothing runner.AddHandler("nop", func(t *state.Task, _ *tomb.Tomb) error { return nil }, nil) // install/update releated runner.AddHandler("prepare-snap", m.doPrepareSnap, m.undoPrepareSnap) runner.AddHandler("download-snap", m.doDownloadSnap, m.undoPrepareSnap) runner.AddHandler("mount-snap", m.doMountSnap, m.undoMountSnap) runner.AddHandler("unlink-current-snap", m.doUnlinkCurrentSnap, m.undoUnlinkCurrentSnap) runner.AddHandler("copy-snap-data", m.doCopySnapData, m.undoCopySnapData) runner.AddHandler("link-snap", m.doLinkSnap, m.undoLinkSnap) // FIXME: port to native tasks and rename //runner.AddHandler("garbage-collect", m.doGarbageCollect, nil) // remove releated runner.AddHandler("unlink-snap", m.doUnlinkSnap, nil) runner.AddHandler("clear-snap", m.doClearSnapData, nil) runner.AddHandler("discard-snap", m.doDiscardSnap, nil) // test handlers runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error { return nil }, nil) runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error { return fmt.Errorf("fake-install-snap-error errored") }, nil) return m, nil }
func (ts *taskRunnerSuite) TestSequenceTests(c *C) { sb := &stateBackend{} st := state.New(sb) r := state.NewTaskRunner(st) defer r.Stop() ch := make(chan string, 256) fn := func(label string) state.HandlerFunc { return func(task *state.Task, tomb *tomb.Tomb) error { st.Lock() defer st.Unlock() ch <- task.Summary() + ":" + label var isSet bool if task.Get(label+"-block", &isSet) == nil && isSet { ch <- task.Summary() + ":" + label + "-block" st.Unlock() <-tomb.Dying() st.Lock() ch <- task.Summary() + ":" + label + "-unblock" } if task.Get(label+"-retry", &isSet) == nil && isSet { task.Set(label+"-retry", false) ch <- task.Summary() + ":" + label + "-retry" return state.Retry } if task.Get(label+"-error", &isSet) == nil && isSet { ch <- task.Summary() + ":" + label + "-error" return errors.New("boom") } return nil } } r.AddHandler("do", fn("do"), nil) r.AddHandler("do-undo", fn("do"), fn("undo")) st.Lock() chg := st.NewChange("install", "...") tasks := make(map[string]*state.Task) for _, name := range strings.Fields("t11 t12 t21 t31 t32") { if name == "t12" { tasks[name] = st.NewTask("do", name) } else { tasks[name] = st.NewTask("do-undo", name) } chg.AddTask(tasks[name]) } tasks["t21"].WaitFor(tasks["t11"]) tasks["t21"].WaitFor(tasks["t12"]) tasks["t31"].WaitFor(tasks["t21"]) tasks["t32"].WaitFor(tasks["t21"]) st.Unlock() for _, test := range sequenceTests { c.Logf("-----") c.Logf("Testing setup: %s", test.setup) statuses := make(map[string]state.Status) for s := state.DefaultStatus; s <= state.ErrorStatus; s++ { statuses[strings.ToLower(s.String())] = s } // Reset and prepare initial task state. st.Lock() for _, t := range chg.Tasks() { t.SetStatus(state.DefaultStatus) t.Set("do-error", false) t.Set("do-block", false) t.Set("undo-error", false) t.Set("undo-block", false) } for _, item := range strings.Fields(test.setup) { if item == "chg:abort" { chg.Abort() continue } kv := strings.Split(item, ":") if strings.HasPrefix(kv[1], "was-") { tasks[kv[0]].SetStatus(statuses[kv[1][4:]]) } else { tasks[kv[0]].Set(kv[1], true) } } st.Unlock() // Run change until final. ensureChange(c, r, sb, chg) // Compute order of events observed. var events []string var done bool for !done { select { case ev := <-ch: events = append(events, ev) // Make t11/t12 and t31/t32 always show up in the // same order if they're next to each other. for i := len(events) - 2; i >= 0; i-- { prev := events[i] next := events[i+1] switch strings.Split(next, ":")[1] { case "do-unblock", "undo-unblock": default: if prev[1] == next[1] && prev[2] > next[2] { events[i], events[i+1] = next, prev continue } } break } default: done = true } } c.Logf("Expected result: %s", test.result) c.Assert(strings.Join(events, " "), Equals, test.result, Commentf("setup: %s", test.setup)) // Compute final expected status for tasks. finalStatus := make(map[string]state.Status) // ... default when no handler is called for tname := range tasks { finalStatus[tname] = state.HoldStatus } // ... overwrite based on relevant setup for _, item := range strings.Fields(test.setup) { if item == "chg:abort" && strings.Contains(test.setup, "t12:was-doing") { // t12 has no undo so must hold if asked to abort when was doing. finalStatus["t12"] = state.HoldStatus } kv := strings.Split(item, ":") if !strings.HasPrefix(kv[1], "was-") { continue } switch strings.TrimPrefix(kv[1], "was-") { case "do", "doing", "done": finalStatus[kv[0]] = state.DoneStatus case "abort", "undo", "undoing", "undone": if kv[0] == "t12" { finalStatus[kv[0]] = state.DoneStatus // no undo for t12 } else { finalStatus[kv[0]] = state.UndoneStatus } case "was-error": finalStatus[kv[0]] = state.ErrorStatus case "was-hold": finalStatus[kv[0]] = state.ErrorStatus } } // ... and overwrite based on events observed. for _, ev := range events { kv := strings.Split(ev, ":") switch kv[1] { case "do": finalStatus[kv[0]] = state.DoneStatus case "undo": finalStatus[kv[0]] = state.UndoneStatus case "do-error", "undo-error": finalStatus[kv[0]] = state.ErrorStatus case "do-retry": if kv[0] == "t12" && finalStatus["t11"] == state.ErrorStatus { // t12 has no undo so must hold if asked to abort on retry. finalStatus["t12"] = state.HoldStatus } } } st.Lock() var gotStatus, wantStatus []string for _, task := range chg.Tasks() { gotStatus = append(gotStatus, task.Summary()+":"+task.Status().String()) wantStatus = append(wantStatus, task.Summary()+":"+finalStatus[task.Summary()].String()) } st.Unlock() c.Logf("Expected statuses: %s", strings.Join(wantStatus, " ")) comment := Commentf("calls: %s", test.result) c.Assert(strings.Join(gotStatus, " "), Equals, strings.Join(wantStatus, " "), comment) } }