From: Michael Tremer Date: Wed, 20 Sep 2023 15:23:54 +0000 (+0000) Subject: users: Introduce daily build quotas X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bffb4fc5fe5a82612fa7831cf41bf28959424bfd;p=pbs.git users: Introduce daily build quotas Signed-off-by: Michael Tremer --- diff --git a/src/buildservice/jobs.py b/src/buildservice/jobs.py index b0299d51..6ccb2f3e 100644 --- a/src/buildservice/jobs.py +++ b/src/buildservice/jobs.py @@ -20,8 +20,12 @@ from .errors import * # Setup logging log = logging.getLogger("pbs.jobs") -JOB_QUEUE_CTE = """ - WITH job_queue AS ( +WITH_JOB_QUEUE_CTE = """ + -- Determine all users which exceed their quotas + %s, + + -- Collect all jobs and order them by priority + job_queue AS ( SELECT jobs.*, rank() OVER ( @@ -54,10 +58,13 @@ JOB_QUEUE_CTE = """ jobs.finished_at IS NULL AND jobs.installcheck_succeeded IS TRUE + -- Remove any jobs from users that have exceeded their quota + AND + NOT builds.owner_id IN (SELECT user_id FROM users_with_exceeded_quotas) ORDER BY _rank ) -""" +""" % users.WITH_EXCEEDED_QUOTAS_CTE class Jobs(base.Object): connections = {} @@ -223,13 +230,13 @@ class Queue(base.Object): def __len__(self): res = self.db.get(""" - %s + WITH %s SELECT COUNT(*) AS len FROM job_queue - """ % JOB_QUEUE_CTE) + """ % WITH_JOB_QUEUE_CTE) if res: return res.len @@ -238,7 +245,7 @@ class Queue(base.Object): def get_jobs(self, limit=None): jobs = self.backend.jobs._get_jobs(""" - %s + WITH %s SELECT * @@ -246,7 +253,7 @@ class Queue(base.Object): job_queue LIMIT %%s - """ % JOB_QUEUE_CTE, limit, + """ % WITH_JOB_QUEUE_CTE, limit, ) return list(jobs) @@ -256,7 +263,7 @@ class Queue(base.Object): Returns all jobs that the given builder can process. """ return self.backend.jobs._get_jobs(""" - %s + WITH %s SELECT * @@ -264,7 +271,7 @@ class Queue(base.Object): job_queue WHERE job_queue.arch = ANY(%%s) - """ % JOB_QUEUE_CTE, builder.supported_arches, + """ % WITH_JOB_QUEUE_CTE, builder.supported_arches, ) async def dispatch(self): @@ -736,6 +743,17 @@ class Job(base.DataObject): """ return self.is_pending(installcheck=True) + def is_halted(self): + # Only scratch builds can be halted + if not self.build.owner: + return False + + if self.is_running() or self.has_finished() or self.is_aborted(): + return False + + # Halt if users have exceeded their quota + return self.build.owner.has_exceeded_build_quota() + def is_running(self): """ Returns True if this job is running diff --git a/src/buildservice/users.py b/src/buildservice/users.py index c287317f..ebc3bb31 100644 --- a/src/buildservice/users.py +++ b/src/buildservice/users.py @@ -52,6 +52,38 @@ LDAP_ATTRS = ( "mailAlternateAddress", ) +WITH_EXCEEDED_QUOTAS_CTE = """ + users_with_exceeded_quotas AS ( + SELECT + users.id AS user_id, + SUM(jobs.finished_at - jobs.started_at) AS used_quota + FROM + users + LEFT JOIN + builds ON users.id = builds.owner_id + LEFT JOIN + jobs ON builds.id = jobs.build_id + WHERE + users.deleted_at IS NULL + AND + users.daily_build_quota IS NOT NULL + AND + builds.deleted_at IS NULL + AND + jobs.deleted_at IS NULL + AND + jobs.started_at IS NOT NULL + AND + jobs.finished_at IS NOT NULL + AND + jobs.finished_at >= CURRENT_TIMESTAMP - INTERVAL '24 hours' + GROUP BY + users.id + HAVING + SUM(jobs.finished_at - jobs.started_at) >= users.daily_build_quota + ) +""" + class QuotaExceededError(Exception): pass @@ -637,6 +669,34 @@ class User(base.DataObject): if self.data.bugzilla_api_key: return bugtracker.Bugzilla(self.backend, self.data.bugzilla_api_key) + # Build Quota + + def get_daily_build_quota(self): + return self.data.build_quota + + def set_daily_build_quota(self, quota): + self._set_attribute("daily_build_quota", quota) + + daily_build_quota = property(get_daily_build_quota, set_daily_build_quota) + + def has_exceeded_build_quota(self): + res = self.db.get(""" + WITH %s + + SELECT + 1 AS result + FROM + users_with_exceeded_quotas + WHERE + users_with_exceeded_quotas.user_id = %%s + """ % WITH_EXCEEDED_QUOTAS_CTE, self.id, + ) + + if res and res.result: + return True + + return False + # Storage Quota def get_storage_quota(self): diff --git a/src/database.sql b/src/database.sql index 33f64f64..ba33e806 100644 --- a/src/database.sql +++ b/src/database.sql @@ -1123,7 +1123,8 @@ CREATE TABLE public.users ( storage_quota bigint, perms text[] DEFAULT ARRAY[]::text[] NOT NULL, _attrs bytea, - bugzilla_api_key text + bugzilla_api_key text, + daily_build_quota interval ); diff --git a/src/templates/jobs/modules/list.html b/src/templates/jobs/modules/list.html index bb69b808..c11c0cf7 100644 --- a/src/templates/jobs/modules/list.html +++ b/src/templates/jobs/modules/list.html @@ -34,7 +34,9 @@
- {% if job.is_queued() %} + {% if job.is_halted() %} + {{ _("Halted") }} + {% elif job.is_queued() %} {{ _("Queued") }} {% elif job.is_pending(installcheck=False) %} {{ _("Dependency Problems") }}