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