Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Functional Tests

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

env:
VIRTUALENV_PIP: "20.2.3"

jobs:
build:

#runs-on: ubuntu-latest
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
python: [3.8.14]
tox-env: [pep8, functional]
env:
TOXENV: ${{ matrix.tox-env }}
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install Tox and any other packages
run: |
pip install tox
- name: Running Tox
run: tox

2 changes: 1 addition & 1 deletion rate_limit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.

from .rate_limit import OpenStackRateLimitMiddleware
from .ratelimit import OpenStackRateLimitMiddleware


def main(global_config, **settings):
Expand Down
5 changes: 2 additions & 3 deletions rate_limit/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@


class Constants(object):
"""
Common constants used in various places.
"""
"""Common constants used in various places."""

ratelimit_response = 'ratelimit_response'
blacklist_response = 'blacklist_response'
max_sleep_time_seconds = 'max_sleep_time_seconds'
Expand Down
4 changes: 1 addition & 3 deletions rate_limit/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@


class Logger(object):
"""
Logger that attempts to log and ignores any error.
"""Logger that attempts to log and ignores any error."""

"""
def __init__(self, name, product_name='rate_limit'):
self.__logger = logging.getLogger(name)
try:
Expand Down
4 changes: 1 addition & 3 deletions rate_limit/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ def _get_wildcard_ratelimits(self, ratelimits, target_type_uri):
:param target_type_uri: the target type URI of the request
:return: target type uri ratelimits if exists
"""

ttu_ratelimits = []
pattern_list = [
lr_key for lr_key in ratelimits
Expand All @@ -134,14 +133,13 @@ def _get_wildcard_ratelimits(self, ratelimits, target_type_uri):

def _match(self, uri, pattern_list):
"""
Check if a URI matches to one of the patterns
Check if a URI matches to one of the patterns.

:param uri: URI to check if it matches to one of the patterns
:param pattern_list : patterns to match against the URI
:return: True if path matches a pattern of the list and
pattern as key for self.local_ratelimits.
"""

for pattern in pattern_list:
if uri.startswith(pattern[:-1]):
return True, pattern
Expand Down
18 changes: 9 additions & 9 deletions rate_limit/rate_limit.py → rate_limit/ratelimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@

from datadog.dogstatsd import DogStatsd

from . import backend as rate_limit_backend
from . import common
from . import errors
from . import provider
from . import response
from . import units
from . import log
from rate_limit import backend as rate_limit_backend
from rate_limit import common
from rate_limit import errors
from rate_limit import provider
from rate_limit import response
from rate_limit import units
from rate_limit import log


class OpenStackRateLimitMiddleware(object):
Expand Down Expand Up @@ -146,7 +146,7 @@ def __init__(self, app, **conf):
self.logger.info("OpenStack Rate Limit Middleware ready for requests.")

def _setup_response(self):
"""Setup configurable RateLimitExceededResponse and BlacklistResponse."""
"""Set up configurable RateLimitExceededResponse and BlacklistResponse."""
# Default responses.
ratelimit_response = response.RateLimitExceededResponse()
blacklist_response = response.BlacklistResponse()
Expand Down Expand Up @@ -185,7 +185,7 @@ def _setup_response(self):
self.blacklist_response = blacklist_response

def __setup_limes_ratelimit_provider(self):
"""Setup Limes as provider for rate limits. If not successful fallback to configuration file."""
"""Set up Limes as provider for rate limits. If not successful fallback to configuration file."""
try:
limes_ratelimit_provider = provider.LimesRateLimitProvider(
service_type=self.service_type,
Expand Down
2 changes: 1 addition & 1 deletion rate_limit/tests/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def incr(self, key, delta=1, time=0):
def decr(self, key, delta=1, time=0):
return self.incr(key, delta=-delta, time=time)

def delete(self,key):
def delete(self, key):
try:
del self.store[key]
except KeyError:
Expand Down
29 changes: 14 additions & 15 deletions rate_limit/tests/test_actiongroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import unittest
import os

from rate_limit.rate_limit import OpenStackRateLimitMiddleware
from rate_limit.ratelimit import OpenStackRateLimitMiddleware
from . import fake


Expand All @@ -42,19 +42,18 @@ def test_groups(self):
self.assertIsNotNone(
rl_groups,
"expected rate limit groups to be '{0}' but got '{1}'".format(
"""
groups:
write:
- update
- delete
- update/*
- delete/os-*

read:
- read
- read/list
- read/*/list
""",
"""
groups:
write:
- update
- delete
- update/*
- delete/os-*
read:
- read
- read/list
- read/*/list
""",
rl_groups
)
)
Expand Down Expand Up @@ -95,7 +94,7 @@ def test_mapping(self):
},
{
'action': 'read/rules/list',
'expected':'read/rules/list',
'expected': 'read/rules/list',
},
]

Expand Down
2 changes: 1 addition & 1 deletion rate_limit/tests/test_limesratelimitprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import json

from rate_limit.rate_limit import OpenStackRateLimitMiddleware, provider
from rate_limit import OpenStackRateLimitMiddleware, provider
from . import fake

WORKDIR = os.path.dirname(os.path.realpath(__file__))
Expand Down
141 changes: 67 additions & 74 deletions rate_limit/tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
# under the License.

import os
import time
import unittest

from rate_limit.rate_limit import OpenStackRateLimitMiddleware
from unittest.mock import patch, DEFAULT

from rate_limit.ratelimit import OpenStackRateLimitMiddleware
from rate_limit.response import BlacklistResponse
from rate_limit.response import RateLimitExceededResponse
from . import fake
Expand Down Expand Up @@ -138,102 +139,94 @@ def test_get_rate_limit(self):
"rate limit for '{0} {1}' should be '{2}' but got '{3}'".format(action, target_type_uri, expected_ratelimit, rate_limit)
)

def test_is_ratelimited_swift_local_container_update(self):
@patch.multiple('pyredis.Pool', evalsha=DEFAULT, script_exists=DEFAULT)
def test_is_ratelimited_swift_local_container_update(self, evalsha, script_exists):
scope = '123456'
action = 'update'
target_type_uri = 'account/container'

retry_after = 58
remaining = 0

script_exists.return_value = ['True']
evalsha.return_value = [remaining, retry_after]

# The current configuration as per /fixtures/swift.yaml allows 2r/m for target type URI account/container and action update.
# Thus the first 2 requests should not be rate limited but the 3rd one.
expected = [
# 1st requests not rate limited.
None,
# 2nd request also not rate limited.
None,
# 3rd request should be a rate limit response
RateLimitExceededResponse(
status='498 Rate Limited',
body='Rate Limit Exceeded',
headerlist=[('X-Retry-After', 58), ('X-RateLimit-Retry-After', 58),
('X-RateLimit-Limit', '2r/m'), ('X-RateLimit-Remaining', 0)]
)
]
expected = RateLimitExceededResponse(
status='498 Rate Limited',
body='Rate Limit Exceeded',
headerlist=[('X-Retry-After', 58),
('X-RateLimit-Retry-After', retry_after),
('X-RateLimit-Limit', '2r/m'),
('X-RateLimit-Remaining', remaining)]
)

for i in range(len(expected)):
result = self.app._rate_limit(scope=scope, action=action, target_type_uri=target_type_uri)
time.sleep(1)
is_equal, msg = response_equal(expected[i], result)
self.assertTrue(is_equal, "test #{0} failed: {1}".format(i, msg))
result = self.app._rate_limit(scope=scope, action=action, target_type_uri=target_type_uri)
is_equal, msg = response_equal(expected, result)
self.assertTrue(is_equal, "test failed: {0}".format(msg))

def test_is_ratelimited_swift_local_wildcard_update(self):
@patch.multiple('pyredis.Pool', evalsha=DEFAULT, script_exists=DEFAULT)
def test_is_ratelimited_swift_local_wildcard_update(self, evalsha, script_exists):
scope = '123456'
action = 'update'
target_type_uri = 'account/container/foo_object'

retry_after = 58
remaining = 0

script_exists.return_value = ['True']
evalsha.return_value = [remaining, retry_after]

# The current configuration as per /fixtures/swift.yaml allows 2r/m
# for target type URI account/container and action update
# which is targeted by wildcard pattern account/*
# Thus the first 2 requests should not be rate limited but the 3rd one.
expected = [
# 1st requests not rate limited.
None,
# 2nd request also not rate limited.
None,
# 3rd request should be a rate limit response
RateLimitExceededResponse(
status='498 Rate Limited',
body='Rate Limit Exceeded',
headerlist=[
('X-Retry-After', 58),
('X-RateLimit-Retry-After', 58),
('X-RateLimit-Limit', '2r/m'),
('X-RateLimit-Remaining', 0),
]
)
]
expected = RateLimitExceededResponse(
status='498 Rate Limited',
body='Rate Limit Exceeded',
headerlist=[
('X-Retry-After', retry_after),
('X-RateLimit-Retry-After', retry_after),
('X-RateLimit-Limit', '2r/m'),
('X-RateLimit-Remaining', remaining),
]
)

for i in range(len(expected)):
result = self.app._rate_limit(scope=scope, action=action, target_type_uri=target_type_uri)
time.sleep(1)
is_equal, msg = response_equal(expected[i], result)
self.assertTrue(is_equal, "test #{0} failed: {1}".format(i, msg))
result = self.app._rate_limit(scope=scope, action=action, target_type_uri=target_type_uri)
is_equal, msg = response_equal(expected, result)
self.assertTrue(is_equal, "test failed: {0}".format(msg))

def test_is_ratelimited_swift_local_after_wildcard_update(self):
@patch.multiple('pyredis.Pool', evalsha=DEFAULT, script_exists=DEFAULT)
def test_is_ratelimited_swift_local_after_wildcard_update(self, evalsha, script_exists):
scope = '123456'
action = 'update'
target_type_uri = 'account/container/foo_object/something/else'

retry_after = 58
remaining = 0

script_exists.return_value = ['True']
evalsha.return_value = [remaining, retry_after]

# The current configuration as per /fixtures/swift.yaml allows 4r/m
# for target type URI account/container/foo_object/something/else
# and action update which is goes after wildcard pattern account/*
# Thus the first 4 requests should not be rate limited but the 5rd one.
expected = [
# 1st requests not rate limited.
None,
# 2nd request also not rate limited.
None,
# 3rd request also not rate limited.
None,
# 4th request also not rate limited.
None,
# 5th request should be a rate limit response
RateLimitExceededResponse(
status='498 Rate Limited',
body='Rate Limit Exceeded',
headerlist=[
('X-Retry-After', 58),
('X-RateLimit-Retry-After', 58),
('X-RateLimit-Limit', '2r/m'),
('X-RateLimit-Remaining', 0),
]
)
]
expected = RateLimitExceededResponse(
status='498 Rate Limited',
body='Rate Limit Exceeded',
headerlist=[
('X-Retry-After', 58),
('X-RateLimit-Retry-After', retry_after),
('X-RateLimit-Limit', '4r/m'),
('X-RateLimit-Remaining', remaining),
]
)

for i in range(len(expected)):
result = self.app._rate_limit(scope=scope, action=action, target_type_uri=target_type_uri)
time.sleep(1)
is_equal, msg = response_equal(expected[i], result)
self.assertTrue(is_equal, "test #{0} failed: {1}".format(i, msg))
result = self.app._rate_limit(scope=scope, action=action, target_type_uri=target_type_uri)
is_equal, msg = response_equal(expected, result)
self.assertTrue(is_equal, "test failed: {0}".format(msg))


def response_equal(expected, got):
Expand All @@ -248,14 +241,14 @@ def response_equal(expected, got):
return False, "expected status '{0}' but got '{1}'".format(expected.status, got.status)

if expected.has_body and expected.body != got.body:
return False, "expected body '{0}' but got '{1}'".format(expected.body, got.body)
return False, "expected body '{0}' but got '{1}'".format(expected.body, got.body)

if not expected.has_body and expected.json_body != got.json_body:
return False, "expected json body '{0}' but got '{1}'".format(expected.json_body, got.json_body)
return False, "expected json body '{0}' but got '{1}'".format(expected.json_body, got.json_body)

return True, "items are equal"

if type(expected) != type(got):
if type(expected) is not type(got):
return False, "expected type {0} but got type {1}".format(type(expected), type(got))

# Compare arguments if neither RateLimitResponse nor BlacklistResponse.
Expand Down
Loading