# 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 (
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 = {}
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
def get_jobs(self, limit=None):
jobs = self.backend.jobs._get_jobs("""
- %s
+ WITH %s
SELECT
*
job_queue
LIMIT
%%s
- """ % JOB_QUEUE_CTE, limit,
+ """ % WITH_JOB_QUEUE_CTE, limit,
)
return list(jobs)
Returns all jobs that the given builder can process.
"""
return self.backend.jobs._get_jobs("""
- %s
+ WITH %s
SELECT
*
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):
"""
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
"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
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):
storage_quota bigint,
perms text[] DEFAULT ARRAY[]::text[] NOT NULL,
_attrs bytea,
- bugzilla_api_key text
+ bugzilla_api_key text,
+ daily_build_quota interval
);
</div>
<div class="level-right">
- {% if job.is_queued() %}
+ {% if job.is_halted() %}
+ <span class="tag">{{ _("Halted") }}</span>
+ {% elif job.is_queued() %}
<span class="tag">{{ _("Queued") }}</span>
{% elif job.is_pending(installcheck=False) %}
<span class="tag">{{ _("Dependency Problems") }}</span>