diff --git a/config/settings/base.py b/config/settings/base.py index c37a0561630b6a7622468455f5e267c171f91989..c006b8fae8233d50753482a3031b7b8faf43c980 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -221,7 +221,11 @@ CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', - } + }, + 'throttles': { + 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', + 'LOCATION': 'api_throttle_cache', # Table name + }, } # List of settings for all template engines. Each item is a dict @@ -347,7 +351,11 @@ REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 1e7, + 'DEFAULT_THROTTLE_CLASSES': ( + 'api.throttling.BurstAnonRateThrottle', + ), 'DEFAULT_THROTTLE_RATES': { + 'anon_burst': '3/second', 'event_creation': '1/second', 'annotation' : '10/second', }, diff --git a/gracedb/api/throttling.py b/gracedb/api/throttling.py index 1c7a5e15994228dd51f9acc12cc9cf7c6eaa7e6b..58f270dfb53cc89e47ea3f27becdcc6997231ea0 100644 --- a/gracedb/api/throttling.py +++ b/gracedb/api/throttling.py @@ -1,7 +1,36 @@ -from rest_framework.throttling import UserRateThrottle +from django.core.cache import caches +from rest_framework.throttling import AnonRateThrottle, UserRateThrottle -class PostOrPutUserRateThrottle(UserRateThrottle): + +# NOTE: we have to use database-backed throttles to have a centralized location +# where multiple workers (like in the production instance) can access and +# update the same throttling information. + + +############################################################################### +# Base throttle classes ####################################################### +############################################################################### +class DbCachedThrottleMixin(object): + """Uses a non-default (database-backed) cache""" + cache = caches['throttles'] + + +############################################################################### +# Throttles for unauthenticated users ######################################### +############################################################################### +class BurstAnonRateThrottle(DbCachedThrottleMixin, AnonRateThrottle): + scope = 'anon_burst' + + +class SustainedAnonRateThrottle(DbCachedThrottleMixin, AnonRateThrottle): + scope = 'anon_sustained' + + +############################################################################### +# Throttles for authenticated users ######################################### +############################################################################### +class PostOrPutUserRateThrottle(DbCachedThrottleMixin, UserRateThrottle): def allow_request(self, request, view): """ diff --git a/gracedb/api/v1/events/throttling.py b/gracedb/api/v1/events/throttling.py index 66332e5b4046d666fe7e0b88768650b3e7675806..006ea41ff07c7fadd3789ec6c22df31266c4f0ec 100644 --- a/gracedb/api/v1/events/throttling.py +++ b/gracedb/api/v1/events/throttling.py @@ -4,6 +4,7 @@ from api.throttling import PostOrPutUserRateThrottle class EventCreationThrottle(PostOrPutUserRateThrottle): scope = 'event_creation' + class AnnotationThrottle(PostOrPutUserRateThrottle): scope = 'annotation' diff --git a/gracedb/api/v1/events/views.py b/gracedb/api/v1/events/views.py index 660f83a94470f6ea4da1064cd1d1b961482403b6..e1c62e0313c725ea58570786e31b8446f5322a1c 100644 --- a/gracedb/api/v1/events/views.py +++ b/gracedb/api/v1/events/views.py @@ -34,6 +34,7 @@ from rest_framework.views import APIView from alerts.events.utils import EventAlertIssuer, EventLogAlertIssuer, \ EventVOEventAlertIssuer, EventPermissionsAlertIssuer +from api.throttling import BurstAnonRateThrottle from core.http import check_and_serve_file from core.vfile import VersionedFile from events.buildVOEvent import buildVOEvent, VOEventBuilderException @@ -52,7 +53,7 @@ from events.view_utils import eventToDict, eventLogToDict, labelToDict, \ from search.forms import SimpleSearchForm from search.query.events import parseQuery, ParseException from superevents.models import Superevent -from .throttles import EventCreationThrottle, AnnotationThrottle +from .throttling import EventCreationThrottle, AnnotationThrottle from ...utils import api_reverse # Set up logger @@ -348,7 +349,7 @@ class EventList(APIView): permission_classes = (IsAuthenticated,IsAuthorizedForPipeline) parser_classes = (parsers.MultiPartParser,) renderer_classes = (JSONRenderer, BrowsableAPIRenderer, LigoLwRenderer, TSVRenderer,) - throttle_classes = (EventCreationThrottle,) + throttle_classes = (BurstAnonRateThrottle, EventCreationThrottle,) def get(self, request, *args, **kwargs): @@ -728,7 +729,7 @@ class EventLogList(APIView): POST param 'message' """ permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) - throttle_classes = (AnnotationThrottle,) + throttle_classes = (BurstAnonRateThrottle, AnnotationThrottle,) @event_and_auth_required def get(self, request, event): @@ -874,7 +875,7 @@ class EMBBEventLogList(APIView): POST param 'message' """ permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) - throttle_classes = (AnnotationThrottle,) + throttle_classes = (BurstAnonRateThrottle, AnnotationThrottle,) @event_and_auth_required def get(self, request, event): @@ -939,7 +940,7 @@ class EMObservationList(APIView): POST param 'message' """ permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) - throttle_classes = (AnnotationThrottle,) + throttle_classes = (BurstAnonRateThrottle, AnnotationThrottle,) @event_and_auth_required def get(self, request, event): @@ -1559,7 +1560,7 @@ class VOEventList(APIView): """VOEvent List Resource """ permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) - throttle_classes = (AnnotationThrottle,) + throttle_classes = (BurstAnonRateThrottle, AnnotationThrottle,) @event_and_auth_required def get(self, request, event): @@ -1693,7 +1694,7 @@ class OperatorSignoffList(APIView): At present, this only supports GET """ permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) - throttle_classes = (AnnotationThrottle,) + throttle_classes = (BurstAnonRateThrottle, AnnotationThrottle,) @event_and_auth_required def get(self, request, event):