From ab8195119a3fdd4fd470d31472707a63710be373 Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Fri, 6 Jan 2023 09:39:10 -0500 Subject: [PATCH 1/3] issue two purges for fastly surrogate keys https://developer.fastly.com/learning/concepts/purging/#race-conditions states there are some race conditions we may be running into with shielding enabled. ref: #12214 --- tests/unit/cache/origin/test_fastly.py | 10 +++++++++- warehouse/cache/origin/fastly.py | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/unit/cache/origin/test_fastly.py b/tests/unit/cache/origin/test_fastly.py index b55930b76798..5f68c71a7df0 100644 --- a/tests/unit/cache/origin/test_fastly.py +++ b/tests/unit/cache/origin/test_fastly.py @@ -181,6 +181,14 @@ def test_purge_key_ok(self, monkeypatch): cacher.purge_key("one") assert requests_post.calls == [ + pretend.call( + "https://api.fastly.com/service/the-service-id/purge/one", + headers={ + "Accept": "application/json", + "Fastly-Key": "an api key", + "Fastly-Soft-Purge": "1", + }, + ), pretend.call( "https://api.fastly.com/service/the-service-id/purge/one", headers={ @@ -190,7 +198,7 @@ def test_purge_key_ok(self, monkeypatch): }, ) ] - assert response.raise_for_status.calls == [pretend.call()] + assert response.raise_for_status.calls == [pretend.call(), pretend.call()] @pytest.mark.parametrize("result", [{"status": "fail"}, {}]) def test_purge_key_unsuccessful(self, monkeypatch, result): diff --git a/warehouse/cache/origin/fastly.py b/warehouse/cache/origin/fastly.py index 5ee8a530d319..3dd25bd0152c 100644 --- a/warehouse/cache/origin/fastly.py +++ b/warehouse/cache/origin/fastly.py @@ -10,6 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time import urllib.parse import requests @@ -106,3 +107,11 @@ def purge_key(self, key): if resp.json().get("status") != "ok": raise UnsuccessfulPurgeError(f"Could not purge {key!r}") + + time.sleep(2) + + resp = requests.post(url, headers=headers) + resp.raise_for_status() + + if resp.json().get("status") != "ok": + raise UnsuccessfulPurgeError(f"Could not double purge {key!r}") From ddf6d93b4a4c33ab29bb85532c95fe266e337542 Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Fri, 6 Jan 2023 09:48:36 -0500 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=96=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/cache/origin/test_fastly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/cache/origin/test_fastly.py b/tests/unit/cache/origin/test_fastly.py index 5f68c71a7df0..bf8b5ada3f00 100644 --- a/tests/unit/cache/origin/test_fastly.py +++ b/tests/unit/cache/origin/test_fastly.py @@ -196,7 +196,7 @@ def test_purge_key_ok(self, monkeypatch): "Fastly-Key": "an api key", "Fastly-Soft-Purge": "1", }, - ) + ), ] assert response.raise_for_status.calls == [pretend.call(), pretend.call()] From b0e4f8bda41ff9314c4b88f1270a8bf9cdf41088 Mon Sep 17 00:00:00 2001 From: Ee Durbin Date: Fri, 6 Jan 2023 10:09:53 -0500 Subject: [PATCH 3/3] test for second purge failure --- tests/unit/cache/origin/test_fastly.py | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unit/cache/origin/test_fastly.py b/tests/unit/cache/origin/test_fastly.py index bf8b5ada3f00..f22dba8d3836 100644 --- a/tests/unit/cache/origin/test_fastly.py +++ b/tests/unit/cache/origin/test_fastly.py @@ -10,6 +10,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools + import celery.exceptions import pretend import pytest @@ -226,3 +228,42 @@ def test_purge_key_unsuccessful(self, monkeypatch, result): ) ] assert response.raise_for_status.calls == [pretend.call()] + + @pytest.mark.parametrize( + "result", [[{"status": "ok"}, {"status": "fail"}], [{"status": "ok"}, {}]] + ) + def test_purge_key_second_unsuccessful(self, monkeypatch, result): + cacher = fastly.FastlyCache( + api_key="an api key", service_id="the-service-id", purger=None + ) + + _result = itertools.cycle(result) + response = pretend.stub( + raise_for_status=pretend.call_recorder(lambda: None), + json=lambda: next(_result), + ) + requests_post = pretend.call_recorder(lambda *a, **kw: response) + monkeypatch.setattr(requests, "post", requests_post) + + with pytest.raises(fastly.UnsuccessfulPurgeError): + cacher.purge_key("one") + + assert requests_post.calls == [ + pretend.call( + "https://api.fastly.com/service/the-service-id/purge/one", + headers={ + "Accept": "application/json", + "Fastly-Key": "an api key", + "Fastly-Soft-Purge": "1", + }, + ), + pretend.call( + "https://api.fastly.com/service/the-service-id/purge/one", + headers={ + "Accept": "application/json", + "Fastly-Key": "an api key", + "Fastly-Soft-Purge": "1", + }, + ), + ] + assert response.raise_for_status.calls == [pretend.call(), pretend.call()]