From 978b83d231ede1831074c0fc7a99e9ef23925b1e Mon Sep 17 00:00:00 2001 From: Tanner Prestegard <tanner.prestegard@ligo.org> Date: Wed, 6 Feb 2019 09:31:59 -0600 Subject: [PATCH] Update throttles and add anonymous throttles All throttles now use a database-backed cache since that is the only way to do centralized throttling (important for production deployment with multiple workers). We also add default throttles for anonymous users for the entire API. --- config/settings/base.py | 10 ++++++++- gracedb/api/throttling.py | 33 +++++++++++++++++++++++++++-- gracedb/api/v1/events/throttling.py | 1 + gracedb/api/v1/events/views.py | 15 +++++++------ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/config/settings/base.py b/config/settings/base.py index c37a05616..c006b8fae 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 1c7a5e159..58f270dfb 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 66332e5b4..006ea41ff 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 660f83a94..e1c62e031 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): -- GitLab