Add a test fixture
This adds a fixture that can be used by consuming projects to simulate a set of limits in keystone, without requiring actual keystone. Currently, consumers have to mock oslo.limit internals (at least) in order to do testing. Change-Id: If72050e90ca8b03e26d128c7bbcef6bbea92b501
This commit is contained in:
parent
1175b0f7c1
commit
caa75c1bab
@ -1,6 +1,7 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
openstackdocstheme>=2.2.1 # Apache-2.0
|
||||
reno>=3.1.0 # Apache-2.0
|
||||
sphinx>=2.0.0,!=2.1.0 # BSD
|
||||
|
@ -4,3 +4,4 @@ Using oslo.limit
|
||||
.. toctree::
|
||||
|
||||
usage.rst
|
||||
testing.rst
|
||||
|
30
doc/source/user/testing.rst
Normal file
30
doc/source/user/testing.rst
Normal file
@ -0,0 +1,30 @@
|
||||
=======
|
||||
Testing
|
||||
=======
|
||||
|
||||
To test a project that uses oslo.limit, a fixture is provided. This
|
||||
mocks out the connection to keystone and retrieval of registered and
|
||||
project limits.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oslo_limit import fixture
|
||||
|
||||
class MyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(MyTest, self).setUp()
|
||||
|
||||
# Default limit of 10 widgets
|
||||
registered_limits = {'widgets': 10}
|
||||
|
||||
# project2 gets 20 widgets
|
||||
project_limits = {'project2': {'widgets': 20}}
|
||||
|
||||
self.useFixture(fixture.LimitFixture(registered_limits,
|
||||
project_limits))
|
||||
|
||||
def test_thing(self):
|
||||
# ... use limit.Enforcer() as usual
|
75
oslo_limit/fixture.py
Normal file
75
oslo_limit/fixture.py
Normal file
@ -0,0 +1,75 @@
|
||||
# Copyright 2021 Red Hat, Inc
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures as fixtures
|
||||
|
||||
from openstack.identity.v3 import endpoint
|
||||
from openstack.identity.v3 import limit as keystone_limit
|
||||
|
||||
|
||||
class LimitFixture(fixtures.Fixture):
|
||||
def __init__(self, reglimits, projlimits):
|
||||
"""A fixture for testing code that relies on Keystone Unified Limits.
|
||||
|
||||
:param reglimits: A dictionary of {resource_name: limit} values to
|
||||
simulate registered limits in keystone.
|
||||
:type reglimits: dict
|
||||
:param projlimits: A dictionary of dictionaries defining per-project
|
||||
limits like {project_id: {resource_name: limit}}.
|
||||
As in reality, only per-project overrides need be
|
||||
provided here; any unmentioned projects or
|
||||
resources will take the registered limit defaults.
|
||||
:type reglimits: dict
|
||||
"""
|
||||
self.reglimits = reglimits
|
||||
self.projlimits = projlimits
|
||||
|
||||
def setUp(self):
|
||||
super(LimitFixture, self).setUp()
|
||||
|
||||
# We mock our own cached connection to Keystone
|
||||
self.mock_conn = mock.MagicMock()
|
||||
self.useFixture(fixtures.MockPatch('oslo_limit.limit._SDK_CONNECTION',
|
||||
new=self.mock_conn))
|
||||
|
||||
# Use a flat enforcement model
|
||||
mock_gem = self.useFixture(
|
||||
fixtures.MockPatch('oslo_limit.limit.Enforcer.'
|
||||
'_get_enforcement_model')).mock
|
||||
mock_gem.return_value = 'flat'
|
||||
|
||||
# Fake keystone endpoint; no per-service limit distinction
|
||||
fake_endpoint = endpoint.Endpoint()
|
||||
fake_endpoint.service_id = "service_id"
|
||||
fake_endpoint.region_id = "region_id"
|
||||
self.mock_conn.get_endpoint.return_value = fake_endpoint
|
||||
|
||||
def fake_limits(service_id, region_id, resource_name, project_id=None):
|
||||
this_limit = keystone_limit.Limit()
|
||||
this_limit.resource_name = resource_name
|
||||
if project_id is None:
|
||||
this_limit.default_limit = self.reglimits.get(resource_name)
|
||||
if this_limit.default_limit is None:
|
||||
return iter([None])
|
||||
else:
|
||||
this_limit.resource_limit = \
|
||||
self.projlimits.get(project_id, {}).get(resource_name)
|
||||
if this_limit.resource_limit is None:
|
||||
return iter([None])
|
||||
|
||||
return iter([this_limit])
|
||||
|
||||
self.mock_conn.limits.side_effect = fake_limits
|
||||
self.mock_conn.registered_limits.side_effect = fake_limits
|
92
oslo_limit/tests/test_fixture.py
Normal file
92
oslo_limit/tests/test_fixture.py
Normal file
@ -0,0 +1,92 @@
|
||||
# Copyright 2021 Red Hat, Inc
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from oslo_limit import exception
|
||||
from oslo_limit import fixture
|
||||
from oslo_limit import limit
|
||||
|
||||
|
||||
class TestFixture(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestFixture, self).setUp()
|
||||
|
||||
# Set up some default projects, registered limits,
|
||||
# and project limits
|
||||
reglimits = {'widgets': 100,
|
||||
'sprockets': 50}
|
||||
projlimits = {
|
||||
'project2': {'widgets': 10},
|
||||
}
|
||||
self.useFixture(fixture.LimitFixture(reglimits, projlimits))
|
||||
|
||||
# Some fake usage for projects
|
||||
self.usage = {
|
||||
'project1': {'sprockets': 10,
|
||||
'widgets': 10},
|
||||
'project2': {'sprockets': 3,
|
||||
'widgets': 3},
|
||||
}
|
||||
|
||||
def proj_usage(project_id, resource_names):
|
||||
return self.usage[project_id]
|
||||
|
||||
# An enforcer to play with
|
||||
self.enforcer = limit.Enforcer(proj_usage)
|
||||
|
||||
def test_project_under_registered_limit_only(self):
|
||||
# Project1 has quota of 50 and 100 each, so no problem with
|
||||
# 10+1 usage.
|
||||
self.enforcer.enforce('project1', {'sprockets': 1,
|
||||
'widgets': 1})
|
||||
|
||||
def test_project_over_registered_limit_only(self):
|
||||
# Project1 has quota of 100 widgets, usage of 112 is over
|
||||
# quota.
|
||||
self.assertRaises(exception.ProjectOverLimit,
|
||||
self.enforcer.enforce,
|
||||
'project1', {'sprockets': 1,
|
||||
'widgets': 102})
|
||||
|
||||
def test_project_over_registered_limit(self):
|
||||
# delta=1 should be under the registered limit of 50
|
||||
self.enforcer.enforce('project2', {'sprockets': 1})
|
||||
|
||||
# delta=50 should be over the registered limit of 50
|
||||
self.assertRaises(exception.ProjectOverLimit,
|
||||
self.enforcer.enforce,
|
||||
'project2', {'sprockets': 50})
|
||||
|
||||
def test_project_over_project_limits(self):
|
||||
# delta=7 is usage=10, right at our project limit of 10
|
||||
self.enforcer.enforce('project2', {'widgets': 7})
|
||||
|
||||
# delta=10 is usage 13, over our project limit of 10
|
||||
self.assertRaises(exception.ProjectOverLimit,
|
||||
self.enforcer.enforce,
|
||||
'project2', {'widgets': 10})
|
||||
|
||||
def test_calculate_usage(self):
|
||||
# Make sure the usage calculator works with the fixture too
|
||||
u = self.enforcer.calculate_usage('project2', ['widgets'])['widgets']
|
||||
self.assertEqual(3, u.usage)
|
||||
self.assertEqual(10, u.limit)
|
||||
|
||||
u = self.enforcer.calculate_usage('project1', ['widgets', 'sprockets'])
|
||||
self.assertEqual(10, u['sprockets'].usage)
|
||||
self.assertEqual(10, u['widgets'].usage)
|
||||
# Since project1 has no project limits, make sure we get the
|
||||
# registered limit values
|
||||
self.assertEqual(50, u['sprockets'].limit)
|
||||
self.assertEqual(100, u['widgets'].limit)
|
@ -2,6 +2,7 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
hacking>=3.0.1,<3.1.0 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
stestr>=1.0.0 # Apache-2.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user