Browse Source
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: If72050e90ca8b03e26d128c7bbcef6bbea92b501changes/67/795567/1 1.4.0
6 changed files with 200 additions and 0 deletions
@ -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 |
@ -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 |
@ -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) |
Loading…
Reference in new issue