summaryrefslogtreecommitdiff
diff options
authorMaciej Borzecki <maciej.zenon.borzecki@canonical.com>2022-03-21 12:54:37 +0100
committerMichael Vogt <mvo@ubuntu.com>2022-03-21 12:54:50 +0100
commitbf9e2409232c1b9416851a9c726755e0d20a2f26 (patch)
tree13716295c56f0a2124085070279adced011a7044
parent662f185ead53fc25451db38fe3a8afdd430f0f8b (diff)
overlord/state: add helper for aborting unready lanes
* overlord/state: add helper for aborting unready lanes A helper for aborting all lanes that aren't ready in a given change. An unready lane is one that carries tasks which have not reached a final status yet. Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com> * overlord/state: optimize lane check, test tweaks Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com> * o/state: more tweaks Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com> * overlord/state: simplify checks, tweak commets Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
-rw-r--r--overlord/state/change.go26
-rw-r--r--overlord/state/change_test.go205
2 files changed, 231 insertions, 0 deletions
diff --git a/overlord/state/change.go b/overlord/state/change.go
index 67d5201516..e37a54faaa 100644
--- a/overlord/state/change.go
+++ b/overlord/state/change.go
@@ -520,6 +520,32 @@ func (c *Change) AbortLanes(lanes []int) {
c.abortLanes(lanes, make(map[int]bool), make(map[string]bool))
}
+// AbortUnreadyLanes aborts the tasks from lanes that aren't fully ready, where
+// a ready lane is one in which all tasks are ready.
+func (c *Change) AbortUnreadyLanes() {
+ c.state.writing()
+ c.abortUnreadyLanes()
+}
+
+func (c *Change) abortUnreadyLanes() {
+ lanesWithLiveTasks := map[int]bool{}
+
+ for _, tid := range c.taskIDs {
+ t := c.state.tasks[tid]
+ if !t.Status().Ready() {
+ for _, tlane := range t.Lanes() {
+ lanesWithLiveTasks[tlane] = true
+ }
+ }
+ }
+
+ abortLanes := []int{}
+ for lane := range lanesWithLiveTasks {
+ abortLanes = append(abortLanes, lane)
+ }
+ c.abortLanes(abortLanes, make(map[int]bool), make(map[string]bool))
+}
+
func (c *Change) abortLanes(lanes []int, abortedLanes map[int]bool, seenTasks map[string]bool) {
var hasLive = make(map[int]bool)
var hasDead = make(map[int]bool)
diff --git a/overlord/state/change_test.go b/overlord/state/change_test.go
index fb40563d61..544cbca5f8 100644
--- a/overlord/state/change_test.go
+++ b/overlord/state/change_test.go
@@ -724,3 +724,208 @@ func (ts *taskRunnerSuite) TestAbortLanes(c *C) {
c.Assert(strings.Join(obtained, " "), Equals, strings.Join(expected, " "), Commentf("setup: %s", test.setup))
}
}
+
+//
+// setup and result lines are <task>:<status>[:<lane>,...]
+// order is <task1>-><task2> (implies task2 waits for task 1)
+// "*" as task name means "all remaining".
+//
+var abortUnreadyLanesTests = []struct {
+ setup string
+ order string
+ result string
+}{
+
+ // Some basics.
+ {
+ setup: "*:do",
+ result: "*:hold",
+ }, {
+ setup: "*:done",
+ result: "*:done",
+ }, {
+ setup: "*:error",
+ result: "*:error",
+ },
+
+ // t11 (1) => t12 (1) => t21 (1) => t22 (1)
+ // t31 (2) => t32 (2) => t41 (2) => t42 (2)
+ {
+ setup: "t11:do:1 t12:do:1 t21:do:1 t22:do:1 t31:do:2 t32:do:2 t41:do:2 t42:do:2",
+ order: "t11->t12 t12->t21 t21->t22 t31->t32 t32->t41 t41->t42",
+ result: "*:hold",
+ }, {
+ setup: "t11:done:1 t12:done:1 t21:done:1 t22:done:1 t31:do:2 t32:do:2 t41:do:2 t42:do:2",
+ order: "t11->t12 t12->t21 t21->t22 t31->t32 t32->t41 t41->t42",
+ result: "t11:done t12:done t21:done t22:done t31:hold t32:hold t41:hold t42:hold",
+ }, {
+ setup: "t11:done:1 t12:done:1 t21:done:1 t22:done:1 t31:done:2 t32:done:2 t41:done:2 t42:do:2",
+ order: "t11->t12 t12->t21 t21->t22 t31->t32 t32->t41 t41->t42",
+ result: "t11:done t12:done t21:done t22:done t31:undo t32:undo t41:undo t42:hold",
+ },
+ // => t21 (2) => t22 (2)
+ // / \
+ // t11 (2,3) => t12 (2,3) => t41 (4) => t42 (4)
+ // \ /
+ // => t31 (3) => t32 (3)
+ {
+ setup: "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t12->t21 t12->t31 t21->t22 t31->t32 t22->t41 t32->t41 t41->t42",
+ result: "*:hold",
+ }, {
+ setup: "t11:done:2,3 t12:done:2,3 t21:done:2 t22:done:2 t31:doing:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t12->t21 t12->t31 t21->t22 t31->t32 t22->t41 t32->t41 t41->t42",
+ // lane 2 is fully complete so it does not get aborted
+ result: "t11:done t12:done t21:done t22:done t31:abort t32:hold t41:hold t42:hold *:undo",
+ }, {
+ setup: "t11:done:2,3 t12:done:2,3 t21:doing:2 t22:do:2 t31:doing:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t12->t21 t12->t31 t21->t22 t31->t32 t22->t41 t32->t41 t41->t42",
+ result: "t21:abort t22:hold t31:abort t32:hold t41:hold t42:hold *:undo",
+ },
+
+ // t11 (1) => t12 (1)
+ // t21 (2) => t22 (2)
+ // t31 (3) => t32 (3)
+ // t41 (4) => t42 (4)
+ {
+ setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t21->t22 t31->t32 t41->t42",
+ result: "*:hold",
+ }, {
+ setup: "t11:do:1 t12:do:1 t21:doing:2 t22:do:2 t31:done:3 t32:doing:3 t41:undone:4 t42:error:4",
+ order: "t11->t12 t21->t22 t31->t32 t41->t42",
+ result: "t11:hold t12:hold t21:abort t22:hold t31:undo t32:abort t41:undone t42:error",
+ },
+ // auto refresh like arrangement
+ //
+ // (apps)
+ // => t31 (3) => t32 (3)
+ // (snapd) (base) /
+ // t11 (1) => t12 (1) => t21 (2) => t22 (2)
+ // \
+ // => t41 (4) => t42 (4)
+ {
+ setup: "t11:done:1 t12:done:1 t21:done:2 t22:done:2 t31:doing:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t12->t21 t21->t22 t22->t31 t22->t41 t31->t32 t41->t42",
+ result: "t11:done t12:done t21:done t22:done t31:abort *:hold",
+ }, {
+ //
+ setup: "t11:done:1 t12:done:1 t21:done:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t12->t21 t21->t22 t22->t31 t22->t41 t31->t32 t41->t42",
+ result: "t11:done t12:done t21:undo *:hold",
+ },
+ // arrangement with a cyclic dependency between tasks
+ //
+ // /-----------------------------------------\
+ // | |
+ // | => t31 (3) => t32 (3) /
+ // (snapd) v (base) /
+ // t11 (1) => t12 (1) => t21 (2) => t22 (2)
+ // \
+ // => t41 (4) => t42 (4)
+ {
+ setup: "t11:done:1 t12:done:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4",
+ order: "t11->t12 t12->t21 t21->t22 t22->t31 t22->t41 t31->t32 t41->t42 t32->t21",
+ result: "t11:done t12:done *:hold",
+ },
+}
+
+func (ts *taskRunnerSuite) TestAbortUnreadyLanes(c *C) {
+
+ names := strings.Fields("t11 t12 t21 t22 t31 t32 t41 t42")
+
+ for i, test := range abortUnreadyLanesTests {
+ sb := &stateBackend{}
+ st := state.New(sb)
+ r := state.NewTaskRunner(st)
+ defer r.Stop()
+
+ st.Lock()
+ defer st.Unlock()
+
+ c.Assert(len(st.Tasks()), Equals, 0)
+
+ chg := st.NewChange("install", "...")
+ tasks := make(map[string]*state.Task)
+ for _, name := range names {
+ tasks[name] = st.NewTask("do", name)
+ chg.AddTask(tasks[name])
+ }
+
+ c.Logf("----- %v", i)
+ c.Logf("Testing setup: %s", test.setup)
+
+ for _, wp := range strings.Fields(test.order) {
+ pair := strings.Split(wp, "->")
+ c.Assert(pair, HasLen, 2)
+ // task 2 waits for task 1 is denoted as:
+ // task1->task2
+ tasks[pair[1]].WaitFor(tasks[pair[0]])
+ }
+
+ statuses := make(map[string]state.Status)
+ for s := state.DefaultStatus; s <= state.ErrorStatus; s++ {
+ statuses[strings.ToLower(s.String())] = s
+ }
+
+ items := strings.Fields(test.setup)
+ seen := make(map[string]bool)
+ for i := 0; i < len(items); i++ {
+ item := items[i]
+ parts := strings.Split(item, ":")
+ if parts[0] == "*" {
+ c.Assert(i, Equals, len(items)-1, Commentf("*: can only be used as the last entry"))
+ for _, name := range names {
+ if !seen[name] {
+ parts[0] = name
+ items = append(items, strings.Join(parts, ":"))
+ }
+ }
+ continue
+ }
+ seen[parts[0]] = true
+ task := tasks[parts[0]]
+ task.SetStatus(statuses[parts[1]])
+ if len(parts) > 2 {
+ lanes := strings.Split(parts[2], ",")
+ for _, lane := range lanes {
+ n, err := strconv.Atoi(lane)
+ c.Assert(err, IsNil)
+ task.JoinLane(n)
+ }
+ }
+ }
+
+ c.Logf("Aborting")
+
+ chg.AbortUnreadyLanes()
+
+ c.Logf("Expected result: %s", test.result)
+
+ seen = make(map[string]bool)
+ var expected = strings.Fields(test.result)
+ var obtained []string
+ for i := 0; i < len(expected); i++ {
+ item := expected[i]
+ parts := strings.Split(item, ":")
+ if parts[0] == "*" {
+ c.Assert(i, Equals, len(expected)-1, Commentf("*: can only be used as the last entry"))
+ var expanded []string
+ for _, name := range names {
+ if !seen[name] {
+ parts[0] = name
+ expanded = append(expanded, strings.Join(parts, ":"))
+ }
+ }
+ expected = append(expected[:i], append(expanded, expected[i+1:]...)...)
+ i--
+ continue
+ }
+ name := parts[0]
+ seen[parts[0]] = true
+ obtained = append(obtained, name+":"+strings.ToLower(tasks[name].Status().String()))
+ }
+
+ c.Assert(strings.Join(obtained, " "), Equals, strings.Join(expected, " "), Commentf("setup: %s", test.setup))
+ }
+}