From 811826b0878dd6679bb0983a24b5f97e707fed36 Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Fri, 26 Nov 2021 17:39:12 +0100 Subject: [PATCH] Preserve most recent branch builds Co-authored-by: Alexandre Fayolle Co-authored-by: Laurent Mignon --- README.md | 1 + src/runboat/db.py | 22 +++++++++++++++++++--- tests/test_db.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a4ded0d..b90350c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ For running the builds: For running the controller (runboat itself): - Python 3.10 +- sqlite3 >= 3.25 - `kubectl` - A `KUBECONFIG` or an in-cluster service account that provides access to the namespace where the builds are deployed, with permissions to create and delete Service, Job, diff --git a/src/runboat/db.py b/src/runboat/db.py index c613868..341c220 100644 --- a/src/runboat/db.py +++ b/src/runboat/db.py @@ -187,10 +187,26 @@ class BuildsDb: return [self._build_from_row(row) for row in rows] def oldest_stopped(self, limit: int) -> list[Build]: - """Return a list of oldest stopped builds.""" + """Return a list of oldest stopped builds. + + Exclude the most recent build of each branch that we want to + preserve from eviction. + """ rows = self._con.execute( - "SELECT * FROM builds WHERE status IN (?, ?, ?) " - "ORDER BY last_scaled LIMIT ?", + """\ + SELECT * FROM ( + SELECT + ROW_NUMBER () OVER ( + PARTITION BY repo, target_branch, pr + ORDER BY created DESC + ) AS rownum, + * + FROM builds + ) + WHERE status IN (?, ?, ?) AND (rownum != 1 OR pr IS NOT NULL) + ORDER BY last_scaled + LIMIT ? + """, (BuildStatus.stopping, BuildStatus.stopped, BuildStatus.failed, limit), ).fetchall() return [self._build_from_row(row) for row in rows] diff --git a/tests/test_db.py b/tests/test_db.py index 0261c38..3f8dd00 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -14,6 +14,8 @@ def _make_build( repo: str | None = None, target_branch: str | None = None, pr: int | None = None, + last_scaled: datetime.datetime | None = None, + created: datetime.datetime | None = None, ) -> Build: name = name or "build-a" return Build( @@ -29,8 +31,8 @@ def _make_build( status=status or BuildStatus.starting, init_status=init_status or BuildInitStatus.todo, desired_replicas=0, - last_scaled=datetime.datetime(2021, 10, 1, 12, 0, 0), - created=datetime.datetime(2021, 10, 1, 11, 0, 0), + last_scaled=last_scaled or datetime.datetime(2021, 10, 1, 12, 0, 0), + created=created or datetime.datetime(2021, 10, 1, 11, 0, 0), ) @@ -158,3 +160,40 @@ def test_repos() -> None: db.add(_make_build(name="b1", repo="oca/repo1")) db.add(_make_build(name="b2", repo="oca/repo2")) assert db.repos() == [Repo(name="oca/repo1"), Repo(name="oca/repo2")] + + +def test_oldest_stopped() -> None: + db = BuildsDb() + db.add( + _make_build( + name="b1", + repo="oca/repo1", + target_branch="15.0", + status=BuildStatus.stopped, + last_scaled=datetime.datetime(2021, 10, 11, 12, 0, 0), + created=datetime.datetime(2021, 10, 11, 12, 0, 0), + ) + ) + db.add( + _make_build( + name="b2", + repo="oca/repo1", + target_branch="15.0", + status=BuildStatus.stopped, + last_scaled=datetime.datetime(2021, 10, 11, 12, 0, 2), + created=datetime.datetime(2021, 10, 11, 12, 0, 2), + ) + ) + # a PR that is most recent than the latest branch build + db.add( + _make_build( + name="pr1", + repo="oca/repo1", + target_branch="15.0", + pr=1, + status=BuildStatus.stopped, + last_scaled=datetime.datetime(2021, 10, 11, 12, 0, 4), + created=datetime.datetime(2021, 10, 11, 12, 0, 4), + ) + ) + assert [b.name for b in db.oldest_stopped(limit=3)] == ["b1", "pr1"]