unified limits: discover service ID and region ID
In oslo.limit 2.6.0 service endpoint discovery was added, provided by three new config options: [oslo_limit] endpoint_service_type = ... endpoint_service_name = ... endpoint_region_name = ... We can use the same config options if they are present to lookup the service ID and region ID we need when calling the GET /registered_limits API as part of the resource limit enforcement strategy. This way, the user will not have to configure endpoint_id. This will look for [oslo.limit]endpoint_id first and if it is not set, it will do the discovery. Closes-Bug: #1931875 Change-Id: Ida14303115e00a1460e6bef4b6d25fc68f343a4e
This commit is contained in:
@@ -12,33 +12,80 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import typing as ty
|
||||
|
||||
if ty.TYPE_CHECKING:
|
||||
from openstack import proxy
|
||||
|
||||
from oslo_limit import exception as limit_exceptions
|
||||
from oslo_limit import limit
|
||||
from oslo_log import log as logging
|
||||
|
||||
import nova.conf
|
||||
from nova import utils as nova_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
UNIFIED_LIMITS_DRIVER = "nova.quota.UnifiedLimitsDriver"
|
||||
ENDPOINT = None
|
||||
IDENTITY_CLIENT = None
|
||||
|
||||
|
||||
def use_unified_limits():
|
||||
return CONF.quota.driver == UNIFIED_LIMITS_DRIVER
|
||||
|
||||
|
||||
def _endpoint():
|
||||
global ENDPOINT
|
||||
if ENDPOINT is None:
|
||||
# This is copied from oslo_limit/limit.py
|
||||
endpoint_id = CONF.oslo_limit.endpoint_id
|
||||
if not endpoint_id:
|
||||
raise ValueError("endpoint_id is not configured")
|
||||
enforcer = limit.Enforcer(lambda: None)
|
||||
ENDPOINT = enforcer.connection.get_endpoint(endpoint_id)
|
||||
return ENDPOINT
|
||||
class IdentityClient:
|
||||
connection: 'proxy.Proxy'
|
||||
service_id: str
|
||||
region_id: str
|
||||
|
||||
def __init__(self, connection, service_id, region_id):
|
||||
self.connection = connection
|
||||
self.service_id = service_id
|
||||
self.region_id = region_id
|
||||
|
||||
def registered_limits(self):
|
||||
return list(self.connection.registered_limits(
|
||||
service_id=self.service_id, region_id=self.region_id))
|
||||
|
||||
|
||||
def _identity_client():
|
||||
global IDENTITY_CLIENT
|
||||
if not IDENTITY_CLIENT:
|
||||
connection = nova_utils.get_sdk_adapter(
|
||||
'identity', True, conf_group='oslo_limit')
|
||||
service_id = None
|
||||
region_id = None
|
||||
# Prefer the endpoint_id if present, same as oslo.limit.
|
||||
if CONF.oslo_limit.endpoint_id is not None:
|
||||
endpoint = connection.get_endpoint(CONF.oslo_limit.endpoint_id)
|
||||
service_id = endpoint.service_id
|
||||
region_id = endpoint.region_id
|
||||
elif 'endpoint_service_type' in CONF.oslo_limit:
|
||||
# This must be oslo.limit >= 2.6.0 and this block is more or less
|
||||
# copied from there.
|
||||
if (not CONF.oslo_limit.endpoint_service_type and not
|
||||
CONF.oslo_limit.endpoint_service_name):
|
||||
raise ValueError(
|
||||
'Either endpoint_service_type or endpoint_service_name '
|
||||
'must be set')
|
||||
# Get the service_id for registered limits calls.
|
||||
services = connection.services(
|
||||
type=CONF.oslo_limit.endpoint_service_type,
|
||||
name=CONF.oslo_limit.endpoint_service_name)
|
||||
if len(services) > 1:
|
||||
raise ValueError('Multiple services found')
|
||||
service_id = services[0].id
|
||||
# Get the region_id if region name is configured.
|
||||
# endpoint_region_name was added in oslo.limit 2.6.0.
|
||||
if CONF.oslo_limit.endpoint_region_name:
|
||||
regions = connection.regions(
|
||||
name=CONF.oslo_limit.endpoint_region_name)
|
||||
if len(regions) > 1:
|
||||
raise ValueError('Multiple regions found')
|
||||
region_id = regions[0].id
|
||||
IDENTITY_CLIENT = IdentityClient(connection, service_id, region_id)
|
||||
return IDENTITY_CLIENT
|
||||
|
||||
|
||||
def should_enforce(exc: limit_exceptions.ProjectOverLimit) -> bool:
|
||||
@@ -95,9 +142,7 @@ def should_enforce(exc: limit_exceptions.ProjectOverLimit) -> bool:
|
||||
# resource names however this will do one API call whereas the alternative
|
||||
# is calling GET /registered_limits/{registered_limit_id} for each resource
|
||||
# name.
|
||||
enforcer = limit.Enforcer(lambda: None)
|
||||
registered_limits = list(enforcer.connection.registered_limits(
|
||||
service_id=_endpoint().service_id, region_id=_endpoint().region_id))
|
||||
registered_limits = _identity_client().registered_limits()
|
||||
|
||||
# Make a set of resource names of the registered limits.
|
||||
have_limits_set = {limit.resource_name for limit in registered_limits}
|
||||
|
@@ -329,8 +329,8 @@ class TestCase(base.BaseTestCase):
|
||||
# Reset the global key manager
|
||||
nova.crypto._KEYMGR = None
|
||||
|
||||
# Reset the global endpoint
|
||||
nova.limit.utils.ENDPOINT = None
|
||||
# Reset the global identity client
|
||||
nova.limit.utils.IDENTITY_CLIENT = None
|
||||
|
||||
def _setup_cells(self):
|
||||
"""Setup a normal cellsv2 environment.
|
||||
|
6
nova/tests/fixtures/nova.py
vendored
6
nova/tests/fixtures/nova.py
vendored
@@ -2110,6 +2110,8 @@ class UnifiedLimitsFixture(fixtures.Fixture):
|
||||
'model': {'name': 'flat'}}
|
||||
self.mock_sdk_adapter.get_endpoint.return_value.service_id = None
|
||||
self.mock_sdk_adapter.get_endpoint.return_value.region_id = None
|
||||
self.mock_sdk_adapter.endpoints.return_value = [
|
||||
mock.Mock(service_id=None, region_id=None)]
|
||||
|
||||
# These are Keystone API calls that oslo.limit will also use.
|
||||
self.mock_sdk_adapter.registered_limits.side_effect = (
|
||||
@@ -2119,6 +2121,10 @@ class UnifiedLimitsFixture(fixtures.Fixture):
|
||||
self.create_registered_limit)
|
||||
self.mock_sdk_adapter.create_limit.side_effect = self.create_limit
|
||||
|
||||
# These are calls made for service endpoint discovery in limit/utils.py
|
||||
self.mock_sdk_adapter.services.return_value = [mock.Mock(id=None)]
|
||||
self.mock_sdk_adapter.regions.return_value = [mock.Mock(id=None)]
|
||||
|
||||
self.registered_limits_list = []
|
||||
self.limits_list = []
|
||||
|
||||
|
@@ -16,6 +16,7 @@ from oslo_limit import fixture as limit_fixture
|
||||
from oslo_serialization import base64
|
||||
from oslo_utils.fixture import uuidsentinel as uuids
|
||||
|
||||
import nova.conf
|
||||
from nova import context as nova_context
|
||||
from nova.limit import local as local_limit
|
||||
from nova.objects import flavor as flavor_obj
|
||||
@@ -24,6 +25,8 @@ from nova.tests import fixtures as nova_fixtures
|
||||
from nova.tests.functional.api import client
|
||||
from nova.tests.functional import integrated_helpers
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
|
||||
class UnifiedLimitsTest(integrated_helpers._IntegratedTestBase):
|
||||
|
||||
@@ -432,3 +435,45 @@ class ResourceStrategyTest(integrated_helpers._IntegratedTestBase):
|
||||
client.OpenStackApiException, self._create_server,
|
||||
api=self.admin_api)
|
||||
self.assertEqual(403, e.response.status_code)
|
||||
|
||||
|
||||
class EndpointDiscoveryTest(UnifiedLimitsTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
if 'endpoint_service_type' not in CONF.oslo_limit:
|
||||
self.skipTest(
|
||||
'oslo.limit < 2.6.0, skipping endpoint discovery tests')
|
||||
# endpoint_id has a default value in the ConfFixture but we want it to
|
||||
# be None so that we do endpoint discovery.
|
||||
self.flags(endpoint_id=None, group='oslo_limit')
|
||||
self.flags(endpoint_service_type='compute', group='oslo_limit')
|
||||
self.flags(endpoint_service_name='nova', group='oslo_limit')
|
||||
|
||||
def test_endpoint_service_type_and_name_not_set(self):
|
||||
self.flags(endpoint_service_type=None, group='oslo_limit')
|
||||
self.flags(endpoint_service_name=None, group='oslo_limit')
|
||||
e = self.assertRaises(
|
||||
client.OpenStackApiException, self._create_server, api=self.api)
|
||||
self.assertEqual(500, e.response.status_code)
|
||||
|
||||
def test_endpoint_service_type_set(self):
|
||||
self.flags(endpoint_service_type='compute', group='oslo_limit')
|
||||
self.flags(endpoint_service_name=None, group='oslo_limit')
|
||||
self._create_server()
|
||||
|
||||
def test_endpoint_service_name_set(self):
|
||||
self.flags(endpoint_service_type=None, group='oslo_limit')
|
||||
self.flags(endpoint_service_name='nova', group='oslo_limit')
|
||||
self._create_server()
|
||||
|
||||
def test_endpoint_service_type_and_name_set(self):
|
||||
self.flags(endpoint_service_type='compute', group='oslo_limit')
|
||||
self.flags(endpoint_service_name='nova', group='oslo_limit')
|
||||
self._create_server()
|
||||
|
||||
def test_endpoint_region_name_set(self):
|
||||
self.flags(endpoint_service_type='compute', group='oslo_limit')
|
||||
self.flags(endpoint_service_name='nova', group='oslo_limit')
|
||||
self.flags(endpoint_region_name='somewhere', group='oslo_limit')
|
||||
self._create_server()
|
||||
|
Reference in New Issue
Block a user