From 93d3e47531942243676342e10f569cda396a42c5 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Wed, 27 Nov 2013 17:32:28 +0800 Subject: [PATCH] Remove middleware ratelimits from v3 api The ratelimit middleware isn't really useful. That is pointed out by https://review.openstack.org/#/c/34821/. And it didn't get any object from mail-list: http://lists.openstack.org/pipermail/openstack-dev/2013-November/020291.html This patch remove ratelimit middleware and limits extension. The config option 'api_rate_limit' is only valid in v2 api. The pipeline factory for v3 api won't check that option anymore. For compatibility, if user is still using old 'api-paste.init', pipeline factory will ignore to load ratelimit middleware, and print warning message to user for notice ratelimit is deprecated in v3 api. Closes-bug: 1255471 DocImpact Change-Id: Ifeae0504e11089f95e4d8af58bcb7372dca87f81 --- doc/v3/api_samples/limits/limit-get-resp.json | 59 -- doc/v3/api_samples/limits/limit-get-resp.xml | 16 - etc/nova/api-paste.ini | 10 +- etc/nova/nova.conf.sample | 5 +- etc/nova/policy.json | 1 - nova/api/auth.py | 31 +- nova/api/openstack/__init__.py | 2 +- .../openstack/compute/plugins/v3/limits.py | 471 ---------- .../compute/plugins/v3/test_limits.py | 867 ------------------ nova/tests/api/test_auth.py | 80 ++ nova/tests/integrated/test_xml.py | 3 + .../limits/limit-get-resp.json.tpl | 59 -- .../api_samples/limits/limit-get-resp.xml.tpl | 16 - nova/tests/integrated/v3/test_limits.py | 30 - setup.cfg | 1 - 15 files changed, 114 insertions(+), 1537 deletions(-) delete mode 100644 doc/v3/api_samples/limits/limit-get-resp.json delete mode 100644 doc/v3/api_samples/limits/limit-get-resp.xml delete mode 100644 nova/api/openstack/compute/plugins/v3/limits.py delete mode 100644 nova/tests/api/openstack/compute/plugins/v3/test_limits.py delete mode 100644 nova/tests/integrated/v3/api_samples/limits/limit-get-resp.json.tpl delete mode 100644 nova/tests/integrated/v3/api_samples/limits/limit-get-resp.xml.tpl delete mode 100644 nova/tests/integrated/v3/test_limits.py diff --git a/doc/v3/api_samples/limits/limit-get-resp.json b/doc/v3/api_samples/limits/limit-get-resp.json deleted file mode 100644 index 4962b8426a2f..000000000000 --- a/doc/v3/api_samples/limits/limit-get-resp.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "limits": { - "rate": [ - { - "limit": [ - { - "next-available": "2013-09-09T13:37:32Z", - "remaining": 10, - "unit": "MINUTE", - "value": 10, - "verb": "POST" - }, - { - "next-available": "2013-09-09T13:37:32Z", - "remaining": 10, - "unit": "MINUTE", - "value": 10, - "verb": "PUT" - }, - { - "next-available": "2013-09-09T13:37:32Z", - "remaining": 100, - "unit": "MINUTE", - "value": 100, - "verb": "DELETE" - } - ], - "regex": ".*", - "uri": "*" - }, - { - "limit": [ - { - "next-available": "2013-09-09T13:37:32Z", - "remaining": 50, - "unit": "DAY", - "value": 50, - "verb": "POST" - } - ], - "regex": "^/servers", - "uri": "*/servers" - }, - { - "limit": [ - { - "next-available": "2013-09-09T13:37:32Z", - "remaining": 3, - "unit": "MINUTE", - "value": 3, - "verb": "GET" - } - ], - "regex": ".*changes_since.*", - "uri": "*changes_since*" - } - ] - } -} \ No newline at end of file diff --git a/doc/v3/api_samples/limits/limit-get-resp.xml b/doc/v3/api_samples/limits/limit-get-resp.xml deleted file mode 100644 index f6747c5e8ff8..000000000000 --- a/doc/v3/api_samples/limits/limit-get-resp.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index 838fcafaecf6..5a0fc66b6461 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -70,10 +70,9 @@ keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2 [composite:openstack_compute_api_v3] -use = call:nova.api.auth:pipeline_factory -noauth = faultwrap sizelimit noauth_v3 ratelimit_v3 osapi_compute_app_v3 -keystone = faultwrap sizelimit authtoken keystonecontext ratelimit_v3 osapi_compute_app_v3 -keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 +use = call:nova.api.auth:pipeline_factory_v3 +noauth = faultwrap sizelimit noauth_v3 osapi_compute_app_v3 +keystone = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -87,9 +86,6 @@ paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory [filter:ratelimit] paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory -[filter:ratelimit_v3] -paste.filter_factory = nova.api.openstack.compute.plugins.v3.limits:RateLimitingMiddleware.factory - [filter:sizelimit] paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample index f646c623fc53..c144ae896cde 100644 --- a/etc/nova/nova.conf.sample +++ b/etc/nova/nova.conf.sample @@ -320,8 +320,9 @@ # Options defined in nova.api.auth # -# whether to use per-user rate limiting for the api. (boolean -# value) +# whether to use per-user rate limiting for the api. This +# option is only used by v2 api. rate limiting is removed from +# v3 api. (boolean value) #api_rate_limit=false # The strategy to use for auth: noauth or keystone. (string diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 72adc1c2b0ca..382a1386580b 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -171,7 +171,6 @@ "compute_extension:v3:keypairs:show": "", "compute_extension:v3:keypairs:create": "", "compute_extension:v3:keypairs:delete": "", - "compute_extension:v3:limits:discoverable": "", "compute_extension:multinic": "", "compute_extension:v3:os-multinic": "", "compute_extension:v3:os-multinic:discoverable": "", diff --git a/nova/api/auth.py b/nova/api/auth.py index 95e861baa940..eaff4ffb3afb 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -32,7 +32,9 @@ from nova import wsgi auth_opts = [ cfg.BoolOpt('api_rate_limit', default=False, - help='whether to use per-user rate limiting for the api.'), + help=('whether to use per-user rate limiting for the api. ' + 'This option is only used by v2 api. rate limiting ' + 'is removed from v3 api.')), cfg.StrOpt('auth_strategy', default='noauth', help='The strategy to use for auth: noauth or keystone.'), @@ -48,6 +50,15 @@ CONF.register_opts(auth_opts) LOG = logging.getLogger(__name__) +def _load_pipeline(loader, pipeline): + filters = [loader.get_filter(n) for n in pipeline[:-1]] + app = loader.get_app(pipeline[-1]) + filters.reverse() + for filter in filters: + app = filter(app) + return app + + def pipeline_factory(loader, global_conf, **local_conf): """A paste pipeline replica that keys off of auth_strategy.""" pipeline = local_conf[CONF.auth_strategy] @@ -55,12 +66,18 @@ def pipeline_factory(loader, global_conf, **local_conf): limit_name = CONF.auth_strategy + '_nolimit' pipeline = local_conf.get(limit_name, pipeline) pipeline = pipeline.split() - filters = [loader.get_filter(n) for n in pipeline[:-1]] - app = loader.get_app(pipeline[-1]) - filters.reverse() - for filter in filters: - app = filter(app) - return app + # NOTE (Alex Xu): This is just for configuration file compatibility. + # If the configuration file still contains 'ratelimit_v3', just ignore it. + # We will remove this code at next release (J) + if 'ratelimit_v3' in pipeline: + LOG.warn(_('ratelimit_v3 is removed from v3 api.')) + pipeline.remove('ratelimit_v3') + return _load_pipeline(loader, pipeline) + + +def pipeline_factory_v3(loader, global_conf, **local_conf): + """A paste pipeline replica that keys off of auth_strategy.""" + return _load_pipeline(loader, local_conf[CONF.auth_strategy].split()) class InjectContext(wsgi.Middleware): diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a443f550fc9a..68a336e972c7 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -62,7 +62,7 @@ CONF.register_opts(api_opts, api_opts_group) # the core API and so must be present # TODO(cyeoh): Expand this list as the core APIs are ported to V3 API_V3_CORE_EXTENSIONS = set(['consoles', 'extensions', 'flavors', 'ips', - 'limits', 'servers', 'server-metadata', + 'servers', 'server-metadata', 'keypairs', 'console-output', 'versions', 'flavor-manage', 'flavor-access', 'flavor-extra-specs']) diff --git a/nova/api/openstack/compute/plugins/v3/limits.py b/nova/api/openstack/compute/plugins/v3/limits.py deleted file mode 100644 index 611fd1b94981..000000000000 --- a/nova/api/openstack/compute/plugins/v3/limits.py +++ /dev/null @@ -1,471 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# All Rights Reserved. -# -# 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. - -""" -Module dedicated functions/classes dealing with rate limiting requests. -""" - -import collections -import copy -import httplib -import math -import re -import time - -import webob.dec -import webob.exc - -from nova.api.openstack.compute.views import limits as limits_views -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova.api.openstack import xmlutil -from nova.openstack.common.gettextutils import _ -from nova.openstack.common import importutils -from nova.openstack.common import jsonutils -from nova import quota -from nova import utils -from nova import wsgi as base_wsgi - - -QUOTAS = quota.QUOTAS -LIMITS_PREFIX = "limits." - - -limits_nsmap = {None: xmlutil.XMLNS_COMMON_V10, 'atom': xmlutil.XMLNS_ATOM} - - -class LimitsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('limits', selector='limits') - - rates = xmlutil.SubTemplateElement(root, 'rates') - rate = xmlutil.SubTemplateElement(rates, 'rate', selector='rate') - rate.set('uri', 'uri') - rate.set('regex', 'regex') - limit = xmlutil.SubTemplateElement(rate, 'limit', selector='limit') - limit.set('value', 'value') - limit.set('verb', 'verb') - limit.set('remaining', 'remaining') - limit.set('unit', 'unit') - limit.set('next-available', 'next-available') - - return xmlutil.MasterTemplate(root, 1, nsmap=limits_nsmap) - - -class LimitsController(wsgi.Controller): - """Controller for accessing limits in the OpenStack API.""" - - @extensions.expected_errors(()) - @wsgi.serializers(xml=LimitsTemplate) - def index(self, req): - """Return all global and rate limit information.""" - context = req.environ['nova.context'] - rate_limits = req.environ.get("nova.limits", []) - - builder = limits_views.ViewBuilderV3() - return builder.build(rate_limits) - - -class Limit(object): - """ - Stores information about a limit for HTTP requests. - """ - - UNITS = dict([(v, k) for k, v in utils.TIME_UNITS.items()]) - - def __init__(self, verb, uri, regex, value, unit): - """ - Initialize a new `Limit`. - - @param verb: HTTP verb (POST, PUT, etc.) - @param uri: Human-readable URI - @param regex: Regular expression format for this limit - @param value: Integer number of requests which can be made - @param unit: Unit of measure for the value parameter - """ - self.verb = verb - self.uri = uri - self.regex = regex - self.value = int(value) - self.unit = unit - self.unit_string = self.display_unit().lower() - self.remaining = int(value) - - if value <= 0: - raise ValueError("Limit value must be > 0") - - self.last_request = None - self.next_request = None - - self.water_level = 0 - self.capacity = self.unit - self.request_value = float(self.capacity) / float(self.value) - msg = _("Only %(value)s %(verb)s request(s) can be " - "made to %(uri)s every %(unit_string)s.") - self.error_message = msg % self.__dict__ - - def __call__(self, verb, url): - """ - Represents a call to this limit from a relevant request. - - @param verb: string http verb (POST, GET, etc.) - @param url: string URL - """ - if self.verb != verb or not re.match(self.regex, url): - return - - now = self._get_time() - - if self.last_request is None: - self.last_request = now - - leak_value = now - self.last_request - - self.water_level -= leak_value - self.water_level = max(self.water_level, 0) - self.water_level += self.request_value - - difference = self.water_level - self.capacity - - self.last_request = now - - if difference > 0: - self.water_level -= self.request_value - self.next_request = now + difference - return difference - - cap = self.capacity - water = self.water_level - val = self.value - - self.remaining = math.floor(((cap - water) / cap) * val) - self.next_request = now - - def _get_time(self): - """Retrieve the current time. Broken out for testability.""" - return time.time() - - def display_unit(self): - """Display the string name of the unit.""" - return self.UNITS.get(self.unit, "UNKNOWN") - - def display(self): - """Return a useful representation of this class.""" - return { - "verb": self.verb, - "URI": self.uri, - "regex": self.regex, - "value": self.value, - "remaining": int(self.remaining), - "unit": self.display_unit(), - "resetTime": int(self.next_request or self._get_time()), - } - -# "Limit" format is a dictionary with the HTTP verb, human-readable URI, -# a regular-expression to match, value and unit of measure (PER_DAY, etc.) - -DEFAULT_LIMITS = [ - Limit("POST", "*", ".*", 10, utils.TIME_UNITS['MINUTE']), - Limit("POST", "*/servers", "^/servers", 50, utils.TIME_UNITS['DAY']), - Limit("PUT", "*", ".*", 10, utils.TIME_UNITS['MINUTE']), - Limit("GET", "*changes_since*", ".*changes_since.*", 3, - utils.TIME_UNITS['MINUTE']), - Limit("DELETE", "*", ".*", 100, utils.TIME_UNITS['MINUTE']), -] - - -class RateLimitingMiddleware(base_wsgi.Middleware): - """ - Rate-limits requests passing through this middleware. All limit information - is stored in memory for this implementation. - """ - - def __init__(self, application, limits=None, limiter=None, **kwargs): - """ - Initialize new `RateLimitingMiddleware`, which wraps the given WSGI - application and sets up the given limits. - - @param application: WSGI application to wrap - @param limits: String describing limits - @param limiter: String identifying class for representing limits - - Other parameters are passed to the constructor for the limiter. - """ - base_wsgi.Middleware.__init__(self, application) - - # Select the limiter class - if limiter is None: - limiter = Limiter - else: - limiter = importutils.import_class(limiter) - - # Parse the limits, if any are provided - if limits is not None: - limits = limiter.parse_limits(limits) - - self._limiter = limiter(limits or DEFAULT_LIMITS, **kwargs) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - """ - Represents a single call through this middleware. We should record the - request if we have a limit relevant to it. If no limit is relevant to - the request, ignore it. - - If the request should be rate limited, return a fault telling the user - they are over the limit and need to retry later. - """ - verb = req.method - url = req.url - context = req.environ.get("nova.context") - - if context: - username = context.user_id - else: - username = None - - delay, error = self._limiter.check_for_delay(verb, url, username) - - if delay: - msg = _("This request was rate-limited.") - retry = time.time() + delay - return wsgi.RateLimitFault(msg, error, retry) - - req.environ["nova.limits"] = self._limiter.get_limits(username) - - return self.application - - -class Limiter(object): - """ - Rate-limit checking class which handles limits in memory. - """ - - def __init__(self, limits, **kwargs): - """ - Initialize the new `Limiter`. - - @param limits: List of `Limit` objects - """ - self.limits = copy.deepcopy(limits) - self.levels = collections.defaultdict(lambda: copy.deepcopy(limits)) - - # Pick up any per-user limit information - for key, value in kwargs.items(): - if key.startswith(LIMITS_PREFIX): - username = key[len(LIMITS_PREFIX):] - self.levels[username] = self.parse_limits(value) - - def get_limits(self, username=None): - """ - Return the limits for a given user. - """ - return [limit.display() for limit in self.levels[username]] - - def check_for_delay(self, verb, url, username=None): - """ - Check the given verb/user/user triplet for limit. - - @return: Tuple of delay (in seconds) and error message (or None, None) - """ - delays = [] - - for limit in self.levels[username]: - delay = limit(verb, url) - if delay: - delays.append((delay, limit.error_message)) - - if delays: - delays.sort() - return delays[0] - - return None, None - - # Note: This method gets called before the class is instantiated, - # so this must be either a static method or a class method. It is - # used to develop a list of limits to feed to the constructor. We - # put this in the class so that subclasses can override the - # default limit parsing. - @staticmethod - def parse_limits(limits): - """ - Convert a string into a list of Limit instances. This - implementation expects a semicolon-separated sequence of - parenthesized groups, where each group contains a - comma-separated sequence consisting of HTTP method, - user-readable URI, a URI reg-exp, an integer number of - requests which can be made, and a unit of measure. Valid - values for the latter are "SECOND", "MINUTE", "HOUR", and - "DAY". - - @return: List of Limit instances. - """ - - # Handle empty limit strings - limits = limits.strip() - if not limits: - return [] - - # Split up the limits by semicolon - result = [] - for group in limits.split(';'): - group = group.strip() - if group[:1] != '(' or group[-1:] != ')': - raise ValueError("Limit rules must be surrounded by " - "parentheses") - group = group[1:-1] - - # Extract the Limit arguments - args = [a.strip() for a in group.split(',')] - if len(args) != 5: - raise ValueError("Limit rules must contain the following " - "arguments: verb, uri, regex, value, unit") - - # Pull out the arguments - verb, uri, regex, value, unit = args - - # Upper-case the verb - verb = verb.upper() - - # Convert value--raises ValueError if it's not integer - value = int(value) - - # Convert unit - unit = unit.upper() - if unit not in utils.TIME_UNITS: - raise ValueError("Invalid units specified") - unit = utils.TIME_UNITS[unit] - - # Build a limit - result.append(Limit(verb, uri, regex, value, unit)) - - return result - - -class WsgiLimiter(object): - """ - Rate-limit checking from a WSGI application. Uses an in-memory `Limiter`. - - To use, POST ``/`` with JSON data such as:: - - { - "verb" : GET, - "path" : "/servers" - } - - and receive a 204 No Content, or a 403 Forbidden with an X-Wait-Seconds - header containing the number of seconds to wait before the action would - succeed. - """ - - def __init__(self, limits=None): - """ - Initialize the new `WsgiLimiter`. - - @param limits: List of `Limit` objects - """ - self._limiter = Limiter(limits or DEFAULT_LIMITS) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, request): - """ - Handles a call to this application. Returns 204 if the request is - acceptable to the limiter, else a 403 is returned with a relevant - header indicating when the request *will* succeed. - """ - if request.method != "POST": - raise webob.exc.HTTPMethodNotAllowed() - - try: - info = dict(jsonutils.loads(request.body)) - except ValueError: - raise webob.exc.HTTPBadRequest() - - username = request.path_info_pop() - verb = info.get("verb") - path = info.get("path") - - delay, error = self._limiter.check_for_delay(verb, path, username) - - if delay: - headers = {"X-Wait-Seconds": "%.2f" % delay} - return webob.exc.HTTPForbidden(headers=headers, explanation=error) - else: - return webob.exc.HTTPNoContent() - - -class WsgiLimiterProxy(object): - """ - Rate-limit requests based on answers from a remote source. - """ - - def __init__(self, limiter_address): - """ - Initialize the new `WsgiLimiterProxy`. - - @param limiter_address: IP/port combination of where to request limit - """ - self.limiter_address = limiter_address - - def check_for_delay(self, verb, path, username=None): - body = jsonutils.dumps({"verb": verb, "path": path}) - headers = {"Content-Type": "application/json"} - - conn = httplib.HTTPConnection(self.limiter_address) - - if username: - conn.request("POST", "/%s" % (username), body, headers) - else: - conn.request("POST", "/", body, headers) - - resp = conn.getresponse() - - if 200 >= resp.status < 300: - return None, None - - return resp.getheader("X-Wait-Seconds"), resp.read() or None - - # Note: This method gets called before the class is instantiated, - # so this must be either a static method or a class method. It is - # used to develop a list of limits to feed to the constructor. - # This implementation returns an empty list, since all limit - # decisions are made by a remote server. - @staticmethod - def parse_limits(limits): - """ - Ignore a limits string--simply doesn't apply for the limit - proxy. - - @return: Empty list. - """ - - return [] - - -class Limits(extensions.V3APIExtensionBase): - """Limits rate core API.""" - name = "Limits" - alias = "limits" - namespace = "http://docs.openstack.org/compute/core/limits/v3" - version = 1 - - def get_resources(self): - resources = [extensions.ResourceExtension(self.alias, - LimitsController(), - member_name='limit')] - return resources - - def get_controller_extensions(self): - return [] diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_limits.py b/nova/tests/api/openstack/compute/plugins/v3/test_limits.py deleted file mode 100644 index a1073b17288a..000000000000 --- a/nova/tests/api/openstack/compute/plugins/v3/test_limits.py +++ /dev/null @@ -1,867 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# All Rights Reserved. -# -# 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. - -""" -Tests dealing with HTTP rate-limiting. -""" - -import httplib -import StringIO -from xml.dom import minidom - -from lxml import etree -import webob - -from nova.api.openstack.compute.plugins.v3 import limits -from nova.api.openstack.compute import views -from nova.api.openstack import xmlutil -import nova.context -from nova.openstack.common import jsonutils -from nova import test -from nova.tests import matchers -from nova import utils - - -TEST_LIMITS = [ - limits.Limit("GET", "/delayed", "^/delayed", 1, - utils.TIME_UNITS['MINUTE']), - limits.Limit("POST", "*", ".*", 7, utils.TIME_UNITS['MINUTE']), - limits.Limit("POST", "/servers", "^/servers", 3, - utils.TIME_UNITS['MINUTE']), - limits.Limit("PUT", "*", "", 10, utils.TIME_UNITS['MINUTE']), - limits.Limit("PUT", "/servers", "^/servers", 5, - utils.TIME_UNITS['MINUTE']), -] -NS = { - 'atom': 'http://www.w3.org/2005/Atom', - 'ns': 'http://docs.openstack.org/common/api/v1.0' -} - - -class BaseLimitTestSuite(test.NoDBTestCase): - """Base test suite which provides relevant stubs and time abstraction.""" - - def setUp(self): - super(BaseLimitTestSuite, self).setUp() - self.time = 0.0 - self.stubs.Set(limits.Limit, "_get_time", self._get_time) - - def _get_time(self): - """Return the "time" according to this test suite.""" - return self.time - - -class LimitsControllerTest(BaseLimitTestSuite): - """ - Tests for `limits.LimitsController` class. - """ - - def setUp(self): - """Run before each test.""" - super(LimitsControllerTest, self).setUp() - self.controller = limits.LimitsController() - - def _get_index_request(self, accept_header="application/json"): - """Helper to set routing arguments.""" - request = webob.Request.blank("/") - request.accept = accept_header - request.environ["wsgiorg.routing_args"] = (None, { - "action": "index", - "controller": "", - }) - context = nova.context.RequestContext('testuser', 'testproject') - request.environ["nova.context"] = context - return request - - def _populate_limits(self, request): - """Put limit info into a request.""" - _limits = [ - limits.Limit("GET", "*", ".*", 10, 60).display(), - limits.Limit("POST", "*", ".*", 5, 60 * 60).display(), - limits.Limit("GET", "changes-since*", "changes-since", - 5, 60).display(), - ] - request.environ["nova.limits"] = _limits - return request - - def test_empty_index_json(self): - # Test getting empty limit details in JSON. - request = self._get_index_request() - body = self.controller.index(request) - expected = { - "limits": { - "rate": [] - }, - } - self.assertEqual(expected, body) - - def test_index_json(self): - # Test getting limit details in JSON. - request = self._get_index_request() - request = self._populate_limits(request) - body = self.controller.index(request) - expected = { - "limits": { - "rate": [ - { - "regex": ".*", - "uri": "*", - "limit": [ - { - "verb": "GET", - "next-available": "1970-01-01T00:00:00Z", - "unit": "MINUTE", - "value": 10, - "remaining": 10, - }, - { - "verb": "POST", - "next-available": "1970-01-01T00:00:00Z", - "unit": "HOUR", - "value": 5, - "remaining": 5, - }, - ], - }, - { - "regex": "changes-since", - "uri": "changes-since*", - "limit": [ - { - "verb": "GET", - "next-available": "1970-01-01T00:00:00Z", - "unit": "MINUTE", - "value": 5, - "remaining": 5, - }, - ], - }, - - ] - }, - } - self.assertEqual(expected, body) - - def _populate_limits_diff_regex(self, request): - """Put limit info into a request.""" - _limits = [ - limits.Limit("GET", "*", ".*", 10, 60).display(), - limits.Limit("GET", "*", "*.*", 10, 60).display(), - ] - request.environ["nova.limits"] = _limits - return request - - def test_index_diff_regex(self): - # Test getting limit details in JSON. - request = self._get_index_request() - request = self._populate_limits_diff_regex(request) - body = self.controller.index(request) - expected = { - "limits": { - "rate": [ - { - "regex": ".*", - "uri": "*", - "limit": [ - { - "verb": "GET", - "next-available": "1970-01-01T00:00:00Z", - "unit": "MINUTE", - "value": 10, - "remaining": 10, - }, - ], - }, - { - "regex": "*.*", - "uri": "*", - "limit": [ - { - "verb": "GET", - "next-available": "1970-01-01T00:00:00Z", - "unit": "MINUTE", - "value": 10, - "remaining": 10, - }, - ], - }, - - ] - }, - } - self.assertEqual(expected, body) - - -class MockLimiter(limits.Limiter): - pass - - -class LimitMiddlewareTest(BaseLimitTestSuite): - """ - Tests for the `limits.RateLimitingMiddleware` class. - """ - - @webob.dec.wsgify - def _empty_app(self, request): - """Do-nothing WSGI app.""" - pass - - def setUp(self): - """Prepare middleware for use through fake WSGI app.""" - super(LimitMiddlewareTest, self).setUp() - _limits = '(GET, *, .*, 1, MINUTE)' - self.app = limits.RateLimitingMiddleware(self._empty_app, _limits, - "%s.MockLimiter" % - self.__class__.__module__) - - def test_limit_class(self): - # Test that middleware selected correct limiter class. - self.assertIsInstance(self.app._limiter, MockLimiter) - - def test_good_request(self): - # Test successful GET request through middleware. - request = webob.Request.blank("/") - response = request.get_response(self.app) - self.assertEqual(200, response.status_int) - - def test_limited_request_json(self): - # Test a rate-limited (429) GET request through middleware. - request = webob.Request.blank("/") - response = request.get_response(self.app) - self.assertEqual(200, response.status_int) - - request = webob.Request.blank("/") - response = request.get_response(self.app) - self.assertEqual(response.status_int, 429) - - self.assertIn('Retry-After', response.headers) - retry_after = int(response.headers['Retry-After']) - self.assertAlmostEqual(retry_after, 60, 1) - - body = jsonutils.loads(response.body) - expected = "Only 1 GET request(s) can be made to * every minute." - value = body["overLimit"]["details"].strip() - self.assertEqual(value, expected) - - self.assertIn("retryAfter", body["overLimit"]) - retryAfter = body["overLimit"]["retryAfter"] - self.assertEqual(retryAfter, "60") - - def test_limited_request_xml(self): - # Test a rate-limited (429) response as XML. - request = webob.Request.blank("/") - response = request.get_response(self.app) - self.assertEqual(200, response.status_int) - - request = webob.Request.blank("/") - request.accept = "application/xml" - response = request.get_response(self.app) - self.assertEqual(response.status_int, 429) - - root = minidom.parseString(response.body).childNodes[0] - expected = "Only 1 GET request(s) can be made to * every minute." - - self.assertIsNotNone(root.attributes.getNamedItem("retryAfter")) - retryAfter = root.attributes.getNamedItem("retryAfter").value - self.assertEqual(retryAfter, "60") - - details = root.getElementsByTagName("details") - self.assertEqual(details.length, 1) - - value = details.item(0).firstChild.data.strip() - self.assertEqual(value, expected) - - -class LimitTest(BaseLimitTestSuite): - """ - Tests for the `limits.Limit` class. - """ - - def test_GET_no_delay(self): - # Test a limit handles 1 GET per second. - limit = limits.Limit("GET", "*", ".*", 1, 1) - delay = limit("GET", "/anything") - self.assertIsNone(delay) - self.assertEqual(0, limit.next_request) - self.assertEqual(0, limit.last_request) - - def test_GET_delay(self): - # Test two calls to 1 GET per second limit. - limit = limits.Limit("GET", "*", ".*", 1, 1) - delay = limit("GET", "/anything") - self.assertIsNone(delay) - - delay = limit("GET", "/anything") - self.assertEqual(1, delay) - self.assertEqual(1, limit.next_request) - self.assertEqual(0, limit.last_request) - - self.time += 4 - - delay = limit("GET", "/anything") - self.assertIsNone(delay) - self.assertEqual(4, limit.next_request) - self.assertEqual(4, limit.last_request) - - -class ParseLimitsTest(BaseLimitTestSuite): - """ - Tests for the default limits parser in the in-memory - `limits.Limiter` class. - """ - - def test_invalid(self): - # Test that parse_limits() handles invalid input correctly. - self.assertRaises(ValueError, limits.Limiter.parse_limits, - ';;;;;') - - def test_bad_rule(self): - # Test that parse_limits() handles bad rules correctly. - self.assertRaises(ValueError, limits.Limiter.parse_limits, - 'GET, *, .*, 20, minute') - - def test_missing_arg(self): - # Test that parse_limits() handles missing args correctly. - self.assertRaises(ValueError, limits.Limiter.parse_limits, - '(GET, *, .*, 20)') - - def test_bad_value(self): - # Test that parse_limits() handles bad values correctly. - self.assertRaises(ValueError, limits.Limiter.parse_limits, - '(GET, *, .*, foo, minute)') - - def test_bad_unit(self): - # Test that parse_limits() handles bad units correctly. - self.assertRaises(ValueError, limits.Limiter.parse_limits, - '(GET, *, .*, 20, lightyears)') - - def test_multiple_rules(self): - # Test that parse_limits() handles multiple rules correctly. - try: - l = limits.Limiter.parse_limits('(get, *, .*, 20, minute);' - '(PUT, /foo*, /foo.*, 10, hour);' - '(POST, /bar*, /bar.*, 5, second);' - '(Say, /derp*, /derp.*, 1, day)') - except ValueError as e: - assert False, str(e) - - # Make sure the number of returned limits are correct - self.assertEqual(len(l), 4) - - # Check all the verbs... - expected = ['GET', 'PUT', 'POST', 'SAY'] - self.assertEqual([t.verb for t in l], expected) - - # ...the URIs... - expected = ['*', '/foo*', '/bar*', '/derp*'] - self.assertEqual([t.uri for t in l], expected) - - # ...the regexes... - expected = ['.*', '/foo.*', '/bar.*', '/derp.*'] - self.assertEqual([t.regex for t in l], expected) - - # ...the values... - expected = [20, 10, 5, 1] - self.assertEqual([t.value for t in l], expected) - - # ...and the units... - expected = [utils.TIME_UNITS['MINUTE'], utils.TIME_UNITS['HOUR'], - utils.TIME_UNITS['SECOND'], utils.TIME_UNITS['DAY']] - self.assertEqual([t.unit for t in l], expected) - - -class LimiterTest(BaseLimitTestSuite): - """ - Tests for the in-memory `limits.Limiter` class. - """ - - def setUp(self): - """Run before each test.""" - super(LimiterTest, self).setUp() - userlimits = {'limits.user3': '', - 'limits.user0': '(get, *, .*, 4, minute);' - '(put, *, .*, 2, minute)'} - self.limiter = limits.Limiter(TEST_LIMITS, **userlimits) - - def _check(self, num, verb, url, username=None): - """Check and yield results from checks.""" - for x in xrange(num): - yield self.limiter.check_for_delay(verb, url, username)[0] - - def _check_sum(self, num, verb, url, username=None): - """Check and sum results from checks.""" - results = self._check(num, verb, url, username) - return sum(item for item in results if item) - - def test_no_delay_GET(self): - """ - Simple test to ensure no delay on a single call for a limit verb we - didn"t set. - """ - delay = self.limiter.check_for_delay("GET", "/anything") - self.assertEqual(delay, (None, None)) - - def test_no_delay_PUT(self): - # Simple test to ensure no delay on a single call for a known limit. - delay = self.limiter.check_for_delay("PUT", "/anything") - self.assertEqual(delay, (None, None)) - - def test_delay_PUT(self): - """ - Ensure the 11th PUT will result in a delay of 6.0 seconds until - the next request will be granced. - """ - expected = [None] * 10 + [6.0] - results = list(self._check(11, "PUT", "/anything")) - - self.assertEqual(expected, results) - - def test_delay_POST(self): - """ - Ensure the 8th POST will result in a delay of 6.0 seconds until - the next request will be granced. - """ - expected = [None] * 7 - results = list(self._check(7, "POST", "/anything")) - self.assertEqual(expected, results) - - expected = 60.0 / 7.0 - results = self._check_sum(1, "POST", "/anything") - self.assertAlmostEqual(expected, results, 8) - - def test_delay_GET(self): - # Ensure the 11th GET will result in NO delay. - expected = [None] * 11 - results = list(self._check(11, "GET", "/anything")) - self.assertEqual(expected, results) - - expected = [None] * 4 + [15.0] - results = list(self._check(5, "GET", "/foo", "user0")) - self.assertEqual(expected, results) - - def test_delay_PUT_servers(self): - """ - Ensure PUT on /servers limits at 5 requests, and PUT elsewhere is still - OK after 5 requests...but then after 11 total requests, PUT limiting - kicks in. - """ - # First 6 requests on PUT /servers - expected = [None] * 5 + [12.0] - results = list(self._check(6, "PUT", "/servers")) - self.assertEqual(expected, results) - - # Next 5 request on PUT /anything - expected = [None] * 4 + [6.0] - results = list(self._check(5, "PUT", "/anything")) - self.assertEqual(expected, results) - - def test_delay_PUT_wait(self): - """ - Ensure after hitting the limit and then waiting for the correct - amount of time, the limit will be lifted. - """ - expected = [None] * 10 + [6.0] - results = list(self._check(11, "PUT", "/anything")) - self.assertEqual(expected, results) - - # Advance time - self.time += 6.0 - - expected = [None, 6.0] - results = list(self._check(2, "PUT", "/anything")) - self.assertEqual(expected, results) - - def test_multiple_delays(self): - # Ensure multiple requests still get a delay. - expected = [None] * 10 + [6.0] * 10 - results = list(self._check(20, "PUT", "/anything")) - self.assertEqual(expected, results) - - self.time += 1.0 - - expected = [5.0] * 10 - results = list(self._check(10, "PUT", "/anything")) - self.assertEqual(expected, results) - - expected = [None] * 2 + [30.0] * 8 - results = list(self._check(10, "PUT", "/anything", "user0")) - self.assertEqual(expected, results) - - def test_user_limit(self): - # Test user-specific limits. - self.assertEqual(self.limiter.levels['user3'], []) - self.assertEqual(len(self.limiter.levels['user0']), 2) - - def test_multiple_users(self): - # Tests involving multiple users. - # User0 - expected = [None] * 2 + [30.0] * 8 - results = list(self._check(10, "PUT", "/anything", "user0")) - self.assertEqual(expected, results) - - # User1 - expected = [None] * 10 + [6.0] * 10 - results = list(self._check(20, "PUT", "/anything", "user1")) - self.assertEqual(expected, results) - - # User2 - expected = [None] * 10 + [6.0] * 5 - results = list(self._check(15, "PUT", "/anything", "user2")) - self.assertEqual(expected, results) - - # User3 - expected = [None] * 20 - results = list(self._check(20, "PUT", "/anything", "user3")) - self.assertEqual(expected, results) - - self.time += 1.0 - - # User1 again - expected = [5.0] * 10 - results = list(self._check(10, "PUT", "/anything", "user1")) - self.assertEqual(expected, results) - - self.time += 1.0 - - # User1 again - expected = [4.0] * 5 - results = list(self._check(5, "PUT", "/anything", "user2")) - self.assertEqual(expected, results) - - # User0 again - expected = [28.0] - results = list(self._check(1, "PUT", "/anything", "user0")) - self.assertEqual(expected, results) - - self.time += 28.0 - - expected = [None, 30.0] - results = list(self._check(2, "PUT", "/anything", "user0")) - self.assertEqual(expected, results) - - -class WsgiLimiterTest(BaseLimitTestSuite): - """ - Tests for `limits.WsgiLimiter` class. - """ - - def setUp(self): - """Run before each test.""" - super(WsgiLimiterTest, self).setUp() - self.app = limits.WsgiLimiter(TEST_LIMITS) - - def _request_data(self, verb, path): - """Get data describing a limit request verb/path.""" - return jsonutils.dumps({"verb": verb, "path": path}) - - def _request(self, verb, url, username=None): - """Make sure that POSTing to the given url causes the given username - to perform the given action. Make the internal rate limiter return - delay and make sure that the WSGI app returns the correct response. - """ - if username: - request = webob.Request.blank("/%s" % username) - else: - request = webob.Request.blank("/") - - request.method = "POST" - request.body = self._request_data(verb, url) - response = request.get_response(self.app) - - if "X-Wait-Seconds" in response.headers: - self.assertEqual(response.status_int, 403) - return response.headers["X-Wait-Seconds"] - - self.assertEqual(response.status_int, 204) - - def test_invalid_methods(self): - # Only POSTs should work. - for method in ["GET", "PUT", "DELETE", "HEAD", "OPTIONS"]: - request = webob.Request.blank("/", method=method) - response = request.get_response(self.app) - self.assertEqual(response.status_int, 405) - - def test_good_url(self): - delay = self._request("GET", "/something") - self.assertIsNone(delay) - - def test_escaping(self): - delay = self._request("GET", "/something/jump%20up") - self.assertIsNone(delay) - - def test_response_to_delays(self): - delay = self._request("GET", "/delayed") - self.assertIsNone(delay) - - delay = self._request("GET", "/delayed") - self.assertEqual(delay, '60.00') - - def test_response_to_delays_usernames(self): - delay = self._request("GET", "/delayed", "user1") - self.assertIsNone(delay) - - delay = self._request("GET", "/delayed", "user2") - self.assertIsNone(delay) - - delay = self._request("GET", "/delayed", "user1") - self.assertEqual(delay, '60.00') - - delay = self._request("GET", "/delayed", "user2") - self.assertEqual(delay, '60.00') - - -class FakeHttplibSocket(object): - """ - Fake `httplib.HTTPResponse` replacement. - """ - - def __init__(self, response_string): - """Initialize new `FakeHttplibSocket`.""" - self._buffer = StringIO.StringIO(response_string) - - def makefile(self, _mode, _other): - """Returns the socket's internal buffer.""" - return self._buffer - - -class FakeHttplibConnection(object): - """ - Fake `httplib.HTTPConnection`. - """ - - def __init__(self, app, host): - """ - Initialize `FakeHttplibConnection`. - """ - self.app = app - self.host = host - - def request(self, method, path, body="", headers=None): - """ - Requests made via this connection actually get translated and routed - into our WSGI app, we then wait for the response and turn it back into - an `httplib.HTTPResponse`. - """ - if not headers: - headers = {} - - req = webob.Request.blank(path) - req.method = method - req.headers = headers - req.host = self.host - req.body = body - - resp = str(req.get_response(self.app)) - resp = "HTTP/1.0 %s" % resp - sock = FakeHttplibSocket(resp) - self.http_response = httplib.HTTPResponse(sock) - self.http_response.begin() - - def getresponse(self): - """Return our generated response from the request.""" - return self.http_response - - -def wire_HTTPConnection_to_WSGI(host, app): - """Monkeypatches HTTPConnection so that if you try to connect to host, you - are instead routed straight to the given WSGI app. - - After calling this method, when any code calls - - httplib.HTTPConnection(host) - - the connection object will be a fake. Its requests will be sent directly - to the given WSGI app rather than through a socket. - - Code connecting to hosts other than host will not be affected. - - This method may be called multiple times to map different hosts to - different apps. - - This method returns the original HTTPConnection object, so that the caller - can restore the default HTTPConnection interface (for all hosts). - """ - class HTTPConnectionDecorator(object): - """Wraps the real HTTPConnection class so that when you instantiate - the class you might instead get a fake instance. - """ - - def __init__(self, wrapped): - self.wrapped = wrapped - - def __call__(self, connection_host, *args, **kwargs): - if connection_host == host: - return FakeHttplibConnection(app, host) - else: - return self.wrapped(connection_host, *args, **kwargs) - - oldHTTPConnection = httplib.HTTPConnection - httplib.HTTPConnection = HTTPConnectionDecorator(httplib.HTTPConnection) - return oldHTTPConnection - - -class WsgiLimiterProxyTest(BaseLimitTestSuite): - """ - Tests for the `limits.WsgiLimiterProxy` class. - """ - - def setUp(self): - """ - Do some nifty HTTP/WSGI magic which allows for WSGI to be called - directly by something like the `httplib` library. - """ - super(WsgiLimiterProxyTest, self).setUp() - self.app = limits.WsgiLimiter(TEST_LIMITS) - self.oldHTTPConnection = ( - wire_HTTPConnection_to_WSGI("169.254.0.1:80", self.app)) - self.proxy = limits.WsgiLimiterProxy("169.254.0.1:80") - - def test_200(self): - # Successful request test. - delay = self.proxy.check_for_delay("GET", "/anything") - self.assertEqual(delay, (None, None)) - - def test_403(self): - # Forbidden request test. - delay = self.proxy.check_for_delay("GET", "/delayed") - self.assertEqual(delay, (None, None)) - - delay, error = self.proxy.check_for_delay("GET", "/delayed") - error = error.strip() - - expected = ("60.00", "403 Forbidden\n\nOnly 1 GET request(s) can be " - "made to /delayed every minute.") - - self.assertEqual((delay, error), expected) - - def tearDown(self): - # restore original HTTPConnection object - httplib.HTTPConnection = self.oldHTTPConnection - super(WsgiLimiterProxyTest, self).tearDown() - - -class LimitsViewBuilderTest(test.NoDBTestCase): - def setUp(self): - super(LimitsViewBuilderTest, self).setUp() - self.view_builder = views.limits.ViewBuilderV3() - self.rate_limits = [{"URI": "*", - "regex": ".*", - "value": 10, - "verb": "POST", - "remaining": 2, - "unit": "MINUTE", - "resetTime": 1311272226}, - {"URI": "*/servers", - "regex": "^/servers", - "value": 50, - "verb": "POST", - "remaining": 10, - "unit": "DAY", - "resetTime": 1311272226}] - - def test_build_limits(self): - expected_limits = {"limits": { - "rate": [{ - "uri": "*", - "regex": ".*", - "limit": [{"value": 10, - "verb": "POST", - "remaining": 2, - "unit": "MINUTE", - "next-available": "2011-07-21T18:17:06Z"}]}, - {"uri": "*/servers", - "regex": "^/servers", - "limit": [{"value": 50, - "verb": "POST", - "remaining": 10, - "unit": "DAY", - "next-available": "2011-07-21T18:17:06Z"}]}]}} - - output = self.view_builder.build(self.rate_limits) - self.assertThat(output, matchers.DictMatches(expected_limits)) - - def test_build_limits_empty_limits(self): - expected_limits = {"limits": {"rate": []}} - - abs_limits = {} - rate_limits = [] - output = self.view_builder.build(rate_limits) - self.assertThat(output, matchers.DictMatches(expected_limits)) - - -class LimitsXMLSerializationTest(test.NoDBTestCase): - def test_xml_declaration(self): - serializer = limits.LimitsTemplate() - - fixture = {"limits": { - "rate": []}} - - output = serializer.serialize(fixture) - has_dec = output.startswith("") - self.assertTrue(has_dec) - - def test_index(self): - serializer = limits.LimitsTemplate() - fixture = { - "limits": { - "rate": [{ - "uri": "*", - "regex": ".*", - "limit": [{ - "value": 10, - "verb": "POST", - "remaining": 2, - "unit": "MINUTE", - "next-available": "2011-12-15T22:42:45Z"}]}, - {"uri": "*/servers", - "regex": "^/servers", - "limit": [{ - "value": 50, - "verb": "POST", - "remaining": 10, - "unit": "DAY", - "next-available": "2011-12-15T22:42:45Z"}]}]}} - - output = serializer.serialize(fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'limits', version='v3') - - #verify rate limits - rates = root.xpath('ns:rates/ns:rate', namespaces=NS) - self.assertEqual(len(rates), 2) - for i, rate in enumerate(rates): - for key in ['uri', 'regex']: - self.assertEqual(rate.get(key), - str(fixture['limits']['rate'][i][key])) - rate_limits = rate.xpath('ns:limit', namespaces=NS) - self.assertEqual(len(rate_limits), 1) - for j, limit in enumerate(rate_limits): - for key in ['verb', 'value', 'remaining', 'unit', - 'next-available']: - self.assertEqual(limit.get(key), - str(fixture['limits']['rate'][i]['limit'][j][key])) - - def test_index_no_limits(self): - serializer = limits.LimitsTemplate() - - fixture = {"limits": {"rate": []}} - - output = serializer.serialize(fixture) - root = etree.XML(output) - xmlutil.validate_schema(root, 'limits', version='v3') - - #verify rate limits - rates = root.xpath('ns:rates/ns:rate', namespaces=NS) - self.assertEqual(len(rates), 0) diff --git a/nova/tests/api/test_auth.py b/nova/tests/api/test_auth.py index 6a4dd9130714..c7fab38abb1c 100644 --- a/nova/tests/api/test_auth.py +++ b/nova/tests/api/test_auth.py @@ -13,6 +13,7 @@ # under the License. import json +from oslo.config import cfg import webob import webob.exc @@ -20,6 +21,8 @@ import nova.api.auth from nova.openstack.common.gettextutils import _ from nova import test +CONF = cfg.CONF + class TestNovaKeystoneContextMiddleware(test.NoDBTestCase): @@ -123,3 +126,80 @@ class TestKeystoneMiddlewareRoles(test.NoDBTestCase): response = self.request.get_response(self.middleware) self.assertEqual(response.status, '200 No Roles') + + +class TestPipeLineFactory(test.NoDBTestCase): + + class FakeFilter(object): + def __init__(self, name): + self.name = name + self.obj = None + + def __call__(self, obj): + self.obj = obj + return self + + class FakeApp(object): + def __init__(self, name): + self.name = name + + class FakeLoader(): + def get_filter(self, name): + return TestPipeLineFactory.FakeFilter(name) + + def get_app(self, name): + return TestPipeLineFactory.FakeApp(name) + + def _test_pipeline(self, pipeline, app): + for p in pipeline.split()[:-1]: + self.assertEqual(app.name, p) + self.assertIsInstance(app, TestPipeLineFactory.FakeFilter) + app = app.obj + self.assertEqual(app.name, pipeline.split()[-1]) + self.assertIsInstance(app, TestPipeLineFactory.FakeApp) + + def test_pipeline_factory(self): + fake_pipeline = 'test1 test2 test3' + app = nova.api.auth.pipeline_factory( + TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline) + self._test_pipeline(fake_pipeline, app) + + def test_pipeline_factory_v3(self): + fake_pipeline = 'test1 test2 test3' + app = nova.api.auth.pipeline_factory_v3( + TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline) + self._test_pipeline(fake_pipeline, app) + + def test_pipeline_facotry_with_rate_limits(self): + CONF.set_override('api_rate_limit', True) + CONF.set_override('auth_strategy', 'keystone') + fake_pipeline = 'test1 test2 test3' + app = nova.api.auth.pipeline_factory( + TestPipeLineFactory.FakeLoader(), None, keystone=fake_pipeline) + self._test_pipeline(fake_pipeline, app) + + def test_pipeline_facotry_without_rate_limits(self): + CONF.set_override('auth_strategy', 'keystone') + fake_pipeline1 = 'test1 test2 test3' + fake_pipeline2 = 'test4 test5 test6' + app = nova.api.auth.pipeline_factory( + TestPipeLineFactory.FakeLoader(), None, + keystone_nolimit=fake_pipeline1, + keystone=fake_pipeline2) + self._test_pipeline(fake_pipeline1, app) + + def test_pipeline_facotry_missing_nolimits_pipeline(self): + CONF.set_override('api_rate_limit', False) + CONF.set_override('auth_strategy', 'keystone') + fake_pipeline = 'test1 test2 test3' + app = nova.api.auth.pipeline_factory( + TestPipeLineFactory.FakeLoader(), None, keystone=fake_pipeline) + self._test_pipeline(fake_pipeline, app) + + def test_pipeline_facotry_compatibility_with_v3(self): + CONF.set_override('api_rate_limit', True) + CONF.set_override('auth_strategy', 'keystone') + fake_pipeline = 'test1 ratelimit_v3 test3' + app = nova.api.auth.pipeline_factory( + TestPipeLineFactory.FakeLoader(), None, keystone=fake_pipeline) + self._test_pipeline('test1 test3', app) diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py index 86b188386366..83711e32659f 100644 --- a/nova/tests/integrated/test_xml.py +++ b/nova/tests/integrated/test_xml.py @@ -56,3 +56,6 @@ class XmlTests(integrated_helpers._IntegratedTestBase): class XmlTestsV3(client.TestOpenStackClientV3Mixin, XmlTests): _api_version = 'v3' + + def test_namespace_limits(self): + pass diff --git a/nova/tests/integrated/v3/api_samples/limits/limit-get-resp.json.tpl b/nova/tests/integrated/v3/api_samples/limits/limit-get-resp.json.tpl deleted file mode 100644 index 0821136ad84b..000000000000 --- a/nova/tests/integrated/v3/api_samples/limits/limit-get-resp.json.tpl +++ /dev/null @@ -1,59 +0,0 @@ -{ - "limits": { - "rate": [ - { - "limit": [ - { - "next-available": "%(timestamp)s", - "remaining": 10, - "unit": "MINUTE", - "value": 10, - "verb": "POST" - }, - { - "next-available": "%(timestamp)s", - "remaining": 10, - "unit": "MINUTE", - "value": 10, - "verb": "PUT" - }, - { - "next-available": "%(timestamp)s", - "remaining": 100, - "unit": "MINUTE", - "value": 100, - "verb": "DELETE" - } - ], - "regex": ".*", - "uri": "*" - }, - { - "limit": [ - { - "next-available": "%(timestamp)s", - "remaining": 50, - "unit": "DAY", - "value": 50, - "verb": "POST" - } - ], - "regex": "^/servers", - "uri": "*/servers" - }, - { - "limit": [ - { - "next-available": "%(timestamp)s", - "remaining": 3, - "unit": "MINUTE", - "value": 3, - "verb": "GET" - } - ], - "regex": ".*changes_since.*", - "uri": "*changes_since*" - } - ] - } -} diff --git a/nova/tests/integrated/v3/api_samples/limits/limit-get-resp.xml.tpl b/nova/tests/integrated/v3/api_samples/limits/limit-get-resp.xml.tpl deleted file mode 100644 index 6954bc4fe8d2..000000000000 --- a/nova/tests/integrated/v3/api_samples/limits/limit-get-resp.xml.tpl +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/nova/tests/integrated/v3/test_limits.py b/nova/tests/integrated/v3/test_limits.py deleted file mode 100644 index 17f3893f7f1b..000000000000 --- a/nova/tests/integrated/v3/test_limits.py +++ /dev/null @@ -1,30 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2012 Nebula, Inc. -# Copyright 2013 IBM Corp. -# -# 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 nova.tests.integrated.v3 import api_sample_base - - -class LimitsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3): - extension_name = "limits" - - def test_limits_get(self): - response = self._do_get('limits') - subs = self._get_regexes() - self._verify_response('limit-get-resp', subs, response, 200) - - -class LimitsSampleXmlTest(LimitsSampleJsonTest): - ctype = 'xml' diff --git a/setup.cfg b/setup.cfg index 9332e8b6face..b42ab6289ac5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -89,7 +89,6 @@ nova.api.v3.extensions = ips = nova.api.openstack.compute.plugins.v3.ips:IPs instance_usage_audit_log = nova.api.openstack.compute.plugins.v3.instance_usage_audit_log:InstanceUsageAuditLog keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs - limits = nova.api.openstack.compute.plugins.v3.limits:Limits migrations = nova.api.openstack.compute.plugins.v3.migrations:Migrations multinic = nova.api.openstack.compute.plugins.v3.multinic:Multinic multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate