From 57770493fa9321275aef08272017b31922dec619 Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Tue, 6 Dec 2016 13:59:47 -0500 Subject: [PATCH] build rate limiting: use a rate --- config.py | 8 +++++--- data/queue.py | 6 ++++-- endpoints/building.py | 13 +++++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/config.py b/config.py index ca86ec745..1e1eb5c9d 100644 --- a/config.py +++ b/config.py @@ -400,6 +400,8 @@ class DefaultConfig(object): # Location of the static marketing site. STATIC_SITE_BUCKET = None - # Maximum number of builds allowed to be queued per repository before rejecting requests. - # Values less than zero allow queues of infinite size. - MAX_BUILD_QUEUE_SIZE = -1 + # Count and duration used to produce a rate of builds allowed to be queued per repository before + # rejecting requests. Values less than zero disable rate limiting. + # Example: 10 builds per minute is accomplished by setting ITEMS = 10, SECS = 60 + MAX_BUILD_QUEUE_RATE_ITEMS = -1 + MAX_BUILD_QUEUE_RATE_SECS = -1 diff --git a/data/queue.py b/data/queue.py index 059dd7d86..5cd84726d 100644 --- a/data/queue.py +++ b/data/queue.py @@ -77,9 +77,11 @@ class WorkQueue(object): ._available_jobs(now, name_match_query) .where(~(QueueItem.queue_name << running_query))) - def num_available_jobs(self, prefix): + def num_available_jobs(self, available_min_time, prefix): prefix = prefix.lstrip('/') - return self._available_jobs(datetime.utcnow(), self._name_match_query() + prefix).count() + available = self._available_jobs(datetime.utcnow(), + self._name_match_query() + prefix) + return available.where(QueueItem.available_after >= available_min_time).count() def _name_match_query(self): return '%s%%' % self._canonical_name([self._queue_name] + self._canonical_name_match_list) diff --git a/endpoints/building.py b/endpoints/building.py index c4aa98e6c..a94154efa 100644 --- a/endpoints/building.py +++ b/endpoints/building.py @@ -1,6 +1,8 @@ import logging import json +from datetime import datetime, timedelta + from flask import request from app import app, dockerfile_build_queue @@ -14,7 +16,8 @@ from util.morecollections import AttrDict logger = logging.getLogger(__name__) -MAX_BUILD_QUEUE_SIZE = app.config.get('MAX_BUILD_QUEUE_SIZE', -1) +MAX_BUILD_QUEUE_ITEMS = app.config.get('MAX_BUILD_QUEUE_ITEMS', -1) +MAX_BUILD_QUEUE_SECS = app.config.get('MAX_BUILD_QUEUE_SECS', -1) class MaximumBuildsQueuedException(Exception): @@ -23,9 +26,11 @@ class MaximumBuildsQueuedException(Exception): def start_build(repository, prepared_build, pull_robot_name=None): queue_item_prefix = '%s/%s' % repository.namespace_user.username, repository.name - available_builds = dockerfile_build_queue.num_available_jobs(queue_item_prefix) - if available_builds >= MAX_BUILD_QUEUE_SIZE and MAX_BUILD_QUEUE_SIZE > -1: - raise MaximumBuildsQueuedException() + if MAX_BUILD_QUEUE_ITEMS > 0 and MAX_BUILD_QUEUE_SECS > 0: + available_min = datetime.utcnow() - timedelta(seconds=-MAX_BUILD_QUEUE_SECS) + available_builds = dockerfile_build_queue.num_available_jobs(available_min, queue_item_prefix) + if available_builds > MAX_BUILD_QUEUE_ITEMS: + raise MaximumBuildsQueuedException() host = app.config['SERVER_HOSTNAME'] repo_path = '%s/%s/%s' % (host, repository.namespace_user.username, repository.name)