From 7b642bb2d9bb7fd76d8fb1cef85ce9a36b6b07b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Wed, 24 Jan 2024 18:03:16 +0100 Subject: [PATCH] perf: cache BBB API calls --- web/b3desk/models/bbb.py | 27 ++++ web/b3desk/settings.py | 4 + web/tests/conftest.py | 1 + web/tests/test_bbb_api_caching.py | 198 ++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 web/tests/test_bbb_api_caching.py diff --git a/web/b3desk/models/bbb.py b/web/b3desk/models/bbb.py index 9db7fd4a..9b96c283 100644 --- a/web/b3desk/models/bbb.py +++ b/web/b3desk/models/bbb.py @@ -11,6 +11,7 @@ import hashlib from datetime import datetime from datetime import timezone +from urllib.parse import urlparse from xml.etree import ElementTree import requests @@ -20,6 +21,25 @@ from b3desk.tasks import background_upload +from .. import cache + + +def cache_key(func, caller, prepped, *args, **kwargs): + return prepped.url + + +def caching_exclusion(func, caller, prepped, *args, **kwargs): + """Only read-only methods should be cached.""" + url = urlparse(prepped.url) + endpoint_name = url.path.split("/")[-1] + return prepped.method != "GET" or endpoint_name not in ( + "isMeetingRunning", + "getMeetingInfo", + "getMeetings", + "getRecordings", + "getRecordingTextTracks", + ) + class BBB: """Interface to BBB API.""" @@ -45,12 +65,18 @@ def bbb_request(self, action, method="GET", **kwargs): prepped.prepare_url(prepped.url, params={"checksum": checksum}) return prepped + @cache.memoize( + unless=caching_exclusion, + timeout=current_app.config["BIGBLUEBUTTON_API_CACHE_DURATION"], + ) def bbb_response(self, request): session = requests.Session() current_app.logger.debug("BBB API request %s: %s", request.method, request.url) response = session.send(request) return {c.tag: c.text for c in ElementTree.fromstring(response.content)} + bbb_response.make_cache_key = cache_key + def is_meeting_running(self): """https://docs.bigbluebutton.org/development/api/#ismeetingrunning""" request = self.bbb_request( @@ -257,6 +283,7 @@ def get_meeting_info(self): ) return self.bbb_response(request) + @cache.memoize(timeout=current_app.config["BIGBLUEBUTTON_API_CACHE_DURATION"]) def get_recordings(self): """https://docs.bigbluebutton.org/development/api/#getrecordings""" request = self.bbb_request( diff --git a/web/b3desk/settings.py b/web/b3desk/settings.py index e238284f..9f72cdde 100644 --- a/web/b3desk/settings.py +++ b/web/b3desk/settings.py @@ -1016,3 +1016,7 @@ def get_rie_network_ips( MATOMO_SITE_ID: Optional[str] = None """ID de l’instance B3Desk dans Matomo.""" + + BIGBLUEBUTTON_API_CACHE_DURATION: int = 5 + """Le temps de mise en cache (en secondes) des réponses aux requêtes GET à + l'API BBB.""" diff --git a/web/tests/conftest.py b/web/tests/conftest.py index 692b3a23..f4673546 100644 --- a/web/tests/conftest.py +++ b/web/tests/conftest.py @@ -95,6 +95,7 @@ def configuration(tmp_path, iam_server, iam_client, smtpd): "CACHE_TYPE": "SimpleCache", # Disable cache in unit tests "CACHE_DEFAULT_TIMEOUT": 0, + "BIGBLUEBUTTON_API_CACHE_DURATION": 0, "MEETING_LOGOUT_URL": "https://example.org/logout", "MAIL_MEETING": True, "SMTP_FROM": "from@example.org", diff --git a/web/tests/test_bbb_api_caching.py b/web/tests/test_bbb_api_caching.py new file mode 100644 index 00000000..0947ea78 --- /dev/null +++ b/web/tests/test_bbb_api_caching.py @@ -0,0 +1,198 @@ +import pytest + + +@pytest.fixture +def configuration(configuration): + configuration["BIGBLUEBUTTON_API_CACHE_DURATION"] = 5 + return configuration + + +IS_MEETING_RUNNING_SUCCESS_RESPONSE = """ + + SUCCESS + true + +""" + + +def test_is_meeting_running(meeting, mocker): + """Tests that the requests to the ismeetingrunning endpoint of the BBB API + are cached.""" + + class Response: + content = IS_MEETING_RUNNING_SUCCESS_RESPONSE + + send = mocker.patch("requests.Session.send", return_value=Response) + + assert send.call_count == 0 + + assert meeting.bbb.is_meeting_running() + assert send.call_count == 1 + + assert meeting.bbb.is_meeting_running() + assert send.call_count == 1 + + +GET_RECORDINGS_RESPONSE = """ + + SUCCESS + + + ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124 + c637ba21adcd0191f48f5c4bf23fab0f96ed5c18 + ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124 + Fred's Room + false + true + published + 1530718721124 + 1530718810456 + 3 + 951067 + + https://bbb-analytics.url + false + c637ba21adcd0191f48f5c4bf23fab0f96ed5c18 + Fred's Room + + + unknown + 0 + false + + 1104836 + + + presentation + https://demo.bigbluebutton.org/playback/presentation/2.0/playback.html?meetingId=ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124 + 7177 + 0 + 1104836 + + + Welcome tohttps://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530718721134/thumbnails/thumb-1.png + (this slide left blank for use as a whiteboard)https://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530718721134/thumbnails/thumb-2.png + (this slide left blank for use as a whiteboard)https://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530718721134/thumbnails/thumb-3.png + + + + + video + https://demo.bigbluebutton.org/podcast/ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124/meeting.mp4 + 0 + 0 + 1104836 + + + + + ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111 + c637ba21adcd0191f48f5c4bf23fab0f96ed5c18 + ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111 + Fred's Room + false + true + published + 1530278898111 + 1530281194326 + 7 + 381530 + + Recording title hand written + Fred's Room + c637ba21adcd0191f48f5c4bf23fab0f96ed5c18 + https://bbb-analytics.url + false + + + unknown + 0 + false + + + + podcast + https://demo.bigbluebutton.org/podcast/ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111/audio.ogg + 0 + 33 + + + presentation + https://demo.bigbluebutton.org/playback/presentation/2.0/playback.html?meetingId=ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111 + 139458 + 33 + + + Welcome tohttps://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530278898120/thumbnails/thumb-1.png + (this slide left blank for use as a whiteboard)https://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530278898120/thumbnails/thumb-2.png + (this slide left blank for use as a whiteboard)https://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530278898111/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530278898120/thumbnails/thumb-3.png + + + + + + + +""" + + +def test_get_recordings(meeting, mocker): + """Tests that the requests to the getrecordings endpoint of the BBB API are + cached.""" + + class Response: + content = GET_RECORDINGS_RESPONSE + + send = mocker.patch("requests.Session.send", return_value=Response) + + assert send.call_count == 0 + + recordings = meeting.bbb.get_recordings() + assert len(recordings) == 2 + assert send.call_count == 1 + + recordings = meeting.bbb.get_recordings() + assert len(recordings) == 2 + assert send.call_count == 1 + + +CREATE_RESPONSE = """ + + SUCCESS + Test + 640ab2bae07bedc4c163f679a746f7ab7fb5d1fa-1531155809613 + bbb-none + ap + mp + 1531155809613 + 70757 + 613-555-1234 + Mon Jul 09 17:03:29 UTC 2018 + false + 0 + false + duplicateWarning + This conference was already in existence and may currently be in progress. + +""" + + +def test_create(meeting, mocker): + """Tests that the requests to the create endpoint of the BBB API are NOT + cached.""" + + class Response: + content = CREATE_RESPONSE + + send = mocker.patch("requests.Session.send", return_value=Response) + mocker.patch("requests.post") + + assert send.call_count == 0 + + data = meeting.bbb.create() + assert data["returncode"] == "SUCCESS" + assert send.call_count == 1 + + data = meeting.bbb.create() + assert data["returncode"] == "SUCCESS" + assert send.call_count == 2