mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Cloudflare: Add Support for Rate Limit
This commit is contained in:
@@ -14,7 +14,8 @@ from unittest import TestCase
|
||||
|
||||
from octodns.record import Record, Update
|
||||
from octodns.provider.base import Plan
|
||||
from octodns.provider.cloudflare import CloudflareProvider
|
||||
from octodns.provider.cloudflare import CloudflareProvider, \
|
||||
CloudflareRateLimitError
|
||||
from octodns.provider.yaml import YamlProvider
|
||||
from octodns.zone import Zone
|
||||
|
||||
@@ -52,7 +53,7 @@ class TestCloudflareProvider(TestCase):
|
||||
empty = {'result': [], 'result_info': {'count': 0, 'per_page': 0}}
|
||||
|
||||
def test_populate(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
|
||||
|
||||
# Bad requests
|
||||
with requests_mock() as mock:
|
||||
@@ -103,6 +104,36 @@ class TestCloudflareProvider(TestCase):
|
||||
provider.populate(zone)
|
||||
self.assertEquals(502, ctx.exception.response.status_code)
|
||||
|
||||
# Rate Limit error
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=429,
|
||||
text='{"success":false,"errors":[{"code":10100,'
|
||||
'"message":"More than 1200 requests per 300 seconds '
|
||||
'reached. Please wait and consider throttling your '
|
||||
'request speed"}],"messages":[],"result":null}')
|
||||
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
|
||||
self.assertEquals('CloudflareRateLimitError',
|
||||
type(ctx.exception).__name__)
|
||||
self.assertEquals('More than 1200 requests per 300 seconds '
|
||||
'reached. Please wait and consider throttling '
|
||||
'your request speed', text_type(ctx.exception))
|
||||
|
||||
# Rate Limit error, unknown resp
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=429, text='{}')
|
||||
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
|
||||
self.assertEquals('CloudflareRateLimitError',
|
||||
type(ctx.exception).__name__)
|
||||
self.assertEquals('Cloudflare error', text_type(ctx.exception))
|
||||
|
||||
# Non-existent zone doesn't populate anything
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=200, json=self.empty)
|
||||
@@ -161,7 +192,7 @@ class TestCloudflareProvider(TestCase):
|
||||
self.assertEquals(13, len(again.records))
|
||||
|
||||
def test_apply(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
|
||||
|
||||
provider._request = Mock()
|
||||
|
||||
@@ -280,7 +311,11 @@ class TestCloudflareProvider(TestCase):
|
||||
|
||||
# we don't care about the POST/create return values
|
||||
provider._request.return_value = {}
|
||||
provider._request.side_effect = None
|
||||
|
||||
# Test out the create rate-limit handling, then 9 successes
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
] + ([None] * 3)
|
||||
|
||||
wanted = Zone('unit.tests.', [])
|
||||
wanted.add_record(Record.new(wanted, 'nc', {
|
||||
@@ -316,7 +351,7 @@ class TestCloudflareProvider(TestCase):
|
||||
])
|
||||
|
||||
def test_update_add_swap(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
|
||||
|
||||
provider.zone_records = Mock(return_value=[
|
||||
{
|
||||
@@ -357,6 +392,7 @@ class TestCloudflareProvider(TestCase):
|
||||
|
||||
provider._request = Mock()
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
self.empty, # no zones
|
||||
{
|
||||
'result': {
|
||||
@@ -423,7 +459,7 @@ class TestCloudflareProvider(TestCase):
|
||||
def test_update_delete(self):
|
||||
# We need another run so that we can delete, we can't both add and
|
||||
# delete in one go b/c of swaps
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
|
||||
|
||||
provider.zone_records = Mock(return_value=[
|
||||
{
|
||||
@@ -464,6 +500,7 @@ class TestCloudflareProvider(TestCase):
|
||||
|
||||
provider._request = Mock()
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
self.empty, # no zones
|
||||
{
|
||||
'result': {
|
||||
@@ -1242,3 +1279,64 @@ class TestCloudflareProvider(TestCase):
|
||||
provider = CloudflareProvider('test', token='token 123')
|
||||
headers = provider._sess.headers
|
||||
self.assertEquals('Bearer token 123', headers['Authorization'])
|
||||
|
||||
def test_retry_behavior(self):
|
||||
provider = CloudflareProvider('test', token='token 123',
|
||||
email='email 234', retry_period=0)
|
||||
result = {
|
||||
"success": True,
|
||||
"errors": [],
|
||||
"messages": [],
|
||||
"result": [],
|
||||
"result_info": {
|
||||
"count": 1,
|
||||
"per_page": 50
|
||||
}
|
||||
}
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider._request = Mock()
|
||||
|
||||
# No retry required, just calls and is returned
|
||||
provider._zones = None
|
||||
provider._request.reset_mock()
|
||||
provider._request.side_effect = [result]
|
||||
self.assertEquals([], provider.zone_records(zone))
|
||||
provider._request.assert_has_calls([call('GET', '/zones',
|
||||
params={'page': 1})])
|
||||
|
||||
# One retry required
|
||||
provider._zones = None
|
||||
provider._request.reset_mock()
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
result
|
||||
]
|
||||
self.assertEquals([], provider.zone_records(zone))
|
||||
provider._request.assert_has_calls([call('GET', '/zones',
|
||||
params={'page': 1})])
|
||||
|
||||
# Two retries required
|
||||
provider._zones = None
|
||||
provider._request.reset_mock()
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
CloudflareRateLimitError('{}'),
|
||||
result
|
||||
]
|
||||
self.assertEquals([], provider.zone_records(zone))
|
||||
provider._request.assert_has_calls([call('GET', '/zones',
|
||||
params={'page': 1})])
|
||||
|
||||
# # Exhaust our retries
|
||||
provider._zones = None
|
||||
provider._request.reset_mock()
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError({"errors": [{"message": "first"}]}),
|
||||
CloudflareRateLimitError({"errors": [{"message": "boo"}]}),
|
||||
CloudflareRateLimitError({"errors": [{"message": "boo"}]}),
|
||||
CloudflareRateLimitError({"errors": [{"message": "boo"}]}),
|
||||
CloudflareRateLimitError({"errors": [{"message": "last"}]}),
|
||||
]
|
||||
with self.assertRaises(CloudflareRateLimitError) as ctx:
|
||||
provider.zone_records(zone)
|
||||
self.assertEquals('last', text_type(ctx.exception))
|
||||
|
||||
Reference in New Issue
Block a user