Move existing V2 to legacy_v2

The first step in the code cleanup for removing the API V3 references
is to move the existing default V2 code into a directory named
'legacy_v2'.  This moves the files, and updates all references to
them. This is the first in a patch series of discreet changes to the
codebase structure.

Partial-Bug: #1462901

Change-Id: Ic19f2d019c2c78f1de14a0eced85e84f2d2cec28
This commit is contained in:
EdLeafe 2015-08-10 18:16:49 +00:00 committed by He Jie Xu
parent d4522d649b
commit a7ca5069fc
114 changed files with 627 additions and 583 deletions

View File

@ -21,17 +21,20 @@ WSGI middleware for OpenStack Compute API.
from oslo_config import cfg
import nova.api.openstack
from nova.api.openstack.compute import consoles
from nova.api.openstack.compute import extensions
from nova.api.openstack.compute import flavors
from nova.api.openstack.compute import image_metadata
from nova.api.openstack.compute import images
from nova.api.openstack.compute import ips
from nova.api.openstack.compute import limits
from nova.api.openstack.compute.legacy_v2 import consoles as v2_consoles
from nova.api.openstack.compute.legacy_v2 import extensions as v2_extensions
from nova.api.openstack.compute.legacy_v2 import flavors as v2_flavors
from nova.api.openstack.compute.legacy_v2 import image_metadata \
as v2_image_metadata
from nova.api.openstack.compute.legacy_v2 import images as v2_images
from nova.api.openstack.compute.legacy_v2 import ips as v2_ips
from nova.api.openstack.compute.legacy_v2 import limits as v2_limits
from nova.api.openstack.compute.legacy_v2 import server_metadata \
as v2_server_metadata
from nova.api.openstack.compute.legacy_v2 import servers as v2_servers
from nova.api.openstack.compute.legacy_v2 import versions as \
legacy_v2_versions
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute import server_metadata
from nova.api.openstack.compute import servers
from nova.api.openstack.compute import versions
allow_instance_snapshots_opt = cfg.BoolOpt('allow_instance_snapshots',
default=True,
@ -45,11 +48,11 @@ class APIRouter(nova.api.openstack.APIRouter):
"""Routes requests on the OpenStack API to the appropriate controller
and method.
"""
ExtensionManager = extensions.ExtensionManager
ExtensionManager = v2_extensions.ExtensionManager
def _setup_routes(self, mapper, ext_mgr, init_only):
if init_only is None or 'versions' in init_only:
self.resources['versions'] = versions.create_resource()
self.resources['versions'] = legacy_v2_versions.create_resource()
mapper.connect("versions", "/",
controller=self.resources['versions'],
action='show',
@ -58,7 +61,7 @@ class APIRouter(nova.api.openstack.APIRouter):
mapper.redirect("", "/")
if init_only is None or 'consoles' in init_only:
self.resources['consoles'] = consoles.create_resource()
self.resources['consoles'] = v2_consoles.create_resource()
mapper.resource("console", "consoles",
controller=self.resources['consoles'],
parent_resource=dict(member_name='server',
@ -66,38 +69,39 @@ class APIRouter(nova.api.openstack.APIRouter):
if init_only is None or 'consoles' in init_only or \
'servers' in init_only or 'ips' in init_only:
self.resources['servers'] = servers.create_resource(ext_mgr)
self.resources['servers'] = v2_servers.create_resource(ext_mgr)
mapper.resource("server", "servers",
controller=self.resources['servers'],
collection={'detail': 'GET'},
member={'action': 'POST'})
if init_only is None or 'ips' in init_only:
self.resources['ips'] = ips.create_resource()
self.resources['ips'] = v2_ips.create_resource()
mapper.resource("ip", "ips", controller=self.resources['ips'],
parent_resource=dict(member_name='server',
collection_name='servers'))
if init_only is None or 'images' in init_only:
self.resources['images'] = images.create_resource()
self.resources['images'] = v2_images.create_resource()
mapper.resource("image", "images",
controller=self.resources['images'],
collection={'detail': 'GET'})
if init_only is None or 'limits' in init_only:
self.resources['limits'] = limits.create_resource()
self.resources['limits'] = v2_limits.create_resource()
mapper.resource("limit", "limits",
controller=self.resources['limits'])
if init_only is None or 'flavors' in init_only:
self.resources['flavors'] = flavors.create_resource()
self.resources['flavors'] = v2_flavors.create_resource()
mapper.resource("flavor", "flavors",
controller=self.resources['flavors'],
collection={'detail': 'GET'},
member={'action': 'POST'})
if init_only is None or 'image_metadata' in init_only:
self.resources['image_metadata'] = image_metadata.create_resource()
v2immeta = v2_image_metadata
self.resources['image_metadata'] = v2immeta.create_resource()
image_metadata_controller = self.resources['image_metadata']
mapper.resource("image_meta", "metadata",
@ -113,7 +117,7 @@ class APIRouter(nova.api.openstack.APIRouter):
if init_only is None or 'server_metadata' in init_only:
self.resources['server_metadata'] = \
server_metadata.create_resource()
v2_server_metadata.create_resource()
server_metadata_controller = self.resources['server_metadata']
mapper.resource("server_meta", "metadata",

View File

@ -15,7 +15,7 @@
"""Config Drive extension."""
from nova.api.openstack.compute import servers
from nova.api.openstack.compute.legacy_v2 import servers
from nova.api.openstack import extensions
from nova.api.openstack import wsgi

View File

@ -18,7 +18,7 @@
import webob
import webob.exc
from nova.api.openstack.compute import servers
from nova.api.openstack.compute.legacy_v2 import servers
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.compute import api as compute_api

View File

@ -0,0 +1,457 @@
# 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.
This module handles rate liming at a per-user level, so it should not be used
to prevent intentional Denial of Service attacks, as we can assume a DOS can
easily come through multiple user accounts. DOS protection should be done at a
different layer. Instead this module should be used to protect against
unintentional user actions. With that in mind the limits set here should be
high enough as to not rate-limit any intentional actions.
To find good rate-limit values, check how long requests are taking (see logs)
in your environment to assess your capabilities and multiply out to get
figures.
NOTE: As the rate-limiting here is done in memory, this only works per
process (each process will have its own rate limiting counter).
"""
import collections
import copy
import math
import re
import time
from oslo_serialization import jsonutils
from oslo_utils import importutils
from six.moves import http_client as httplib
import webob.dec
import webob.exc
from nova.api.openstack.compute.views import limits as limits_views
from nova.api.openstack import wsgi
from nova.i18n import _
from nova import quota
from nova import utils
from nova import wsgi as base_wsgi
QUOTAS = quota.QUOTAS
LIMITS_PREFIX = "limits."
class LimitsController(object):
"""Controller for accessing limits in the OpenStack API."""
def index(self, req):
"""Return all global and rate limit information."""
context = req.environ['nova.context']
project_id = req.params.get('tenant_id', context.project_id)
quotas = QUOTAS.get_project_quotas(context, project_id,
usages=False)
abs_limits = {k: v['limit'] for k, v in quotas.items()}
rate_limits = req.environ.get("nova.limits", [])
builder = self._get_view_builder(req)
return builder.build(rate_limits, abs_limits)
def create(self, req, body):
"""Create a new limit."""
raise webob.exc.HTTPNotImplemented()
def delete(self, req, id):
"""Delete the limit."""
raise webob.exc.HTTPNotImplemented()
def show(self, req, id):
"""Show limit information."""
raise webob.exc.HTTPNotImplemented()
def update(self, req, id, body):
"""Update existing limit."""
raise webob.exc.HTTPNotImplemented()
def _get_view_builder(self, req):
return limits_views.ViewBuilder()
def create_resource():
return wsgi.Resource(LimitsController())
class Limit(object):
"""Stores information about a limit for HTTP requests."""
UNITS = {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.") %
{'value': self.value, 'verb': self.verb, 'uri': self.uri,
'unit_string': self.unit_string})
self.error_message = msg
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", "*", ".*", 120, utils.TIME_UNITS['MINUTE']),
Limit("POST", "*/servers", "^/servers", 120, utils.TIME_UNITS['MINUTE']),
Limit("PUT", "*", ".*", 120, utils.TIME_UNITS['MINUTE']),
Limit("GET", "*changes-since*", ".*changes-since.*", 120,
utils.TIME_UNITS['MINUTE']),
Limit("DELETE", "*", ".*", 120, utils.TIME_UNITS['MINUTE']),
Limit("GET", "*/os-fping", "^/os-fping", 12, 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`.
It 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 ``/<username>`` 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 []

View File

@ -0,0 +1,28 @@
# 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.
from nova.api.openstack.compute import versions
from nova.api.openstack.compute.views import versions as views_versions
from nova.api.openstack import wsgi
class VersionV2(object):
def show(self, req):
builder = views_versions.get_view_builder(req)
return builder.build_version(versions.VERSIONS['v2.0'])
def create_resource():
return wsgi.Resource(VersionV2())

View File

@ -13,445 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Module dedicated functions/classes dealing with rate limiting requests.
from nova.api.openstack.compute.legacy_v2 import limits
This module handles rate liming at a per-user level, so it should not be used
to prevent intentional Denial of Service attacks, as we can assume a DOS can
easily come through multiple user accounts. DOS protection should be done at a
different layer. Instead this module should be used to protect against
unintentional user actions. With that in mind the limits set here should be
high enough as to not rate-limit any intentional actions.
To find good rate-limit values, check how long requests are taking (see logs)
in your environment to assess your capabilities and multiply out to get
figures.
NOTE: As the rate-limiting here is done in memory, this only works per
process (each process will have its own rate limiting counter).
"""
import collections
import copy
import math
import re
import time
from oslo_serialization import jsonutils
from oslo_utils import importutils
from six.moves import http_client as httplib
import webob.dec
import webob.exc
from nova.api.openstack.compute.views import limits as limits_views
from nova.api.openstack import wsgi
from nova.i18n import _
from nova import quota
from nova import utils
from nova import wsgi as base_wsgi
QUOTAS = quota.QUOTAS
LIMITS_PREFIX = "limits."
class LimitsController(object):
"""Controller for accessing limits in the OpenStack API."""
def index(self, req):
"""Return all global and rate limit information."""
context = req.environ['nova.context']
project_id = req.params.get('tenant_id', context.project_id)
quotas = QUOTAS.get_project_quotas(context, project_id,
usages=False)
abs_limits = {k: v['limit'] for k, v in quotas.items()}
rate_limits = req.environ.get("nova.limits", [])
builder = self._get_view_builder(req)
return builder.build(rate_limits, abs_limits)
def create(self, req, body):
"""Create a new limit."""
raise webob.exc.HTTPNotImplemented()
def delete(self, req, id):
"""Delete the limit."""
raise webob.exc.HTTPNotImplemented()
def show(self, req, id):
"""Show limit information."""
raise webob.exc.HTTPNotImplemented()
def update(self, req, id, body):
"""Update existing limit."""
raise webob.exc.HTTPNotImplemented()
def _get_view_builder(self, req):
return limits_views.ViewBuilder()
def create_resource():
return wsgi.Resource(LimitsController())
class Limit(object):
"""Stores information about a limit for HTTP requests."""
UNITS = {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.") %
{'value': self.value, 'verb': self.verb, 'uri': self.uri,
'unit_string': self.unit_string})
self.error_message = msg
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", "*", ".*", 120, utils.TIME_UNITS['MINUTE']),
Limit("POST", "*/servers", "^/servers", 120, utils.TIME_UNITS['MINUTE']),
Limit("PUT", "*", ".*", 120, utils.TIME_UNITS['MINUTE']),
Limit("GET", "*changes-since*", ".*changes-since.*", 120,
utils.TIME_UNITS['MINUTE']),
Limit("DELETE", "*", ".*", 120, utils.TIME_UNITS['MINUTE']),
Limit("GET", "*/os-fping", "^/os-fping", 12, 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`.
It 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 ``/<username>`` 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 []
# NOTE(alex_xu): This is just for keeping backward compatible with v2 endpoint
# in api-paste.ini. This will be removed after v2 API code deprecated in the
# future.
RateLimitingMiddleware = limits.RateLimitingMiddleware

View File

@ -29,7 +29,7 @@ from nova import utils
CONF = cfg.CONF
CONF.import_opt('enable_instance_password',
'nova.api.openstack.compute.servers')
'nova.api.openstack.compute.legacy_v2.servers')
ALIAS = "os-evacuate"
authorize = extensions.os_compute_authorizer(ALIAS)

View File

@ -30,7 +30,7 @@ from nova import utils
ALIAS = "os-rescue"
CONF = cfg.CONF
CONF.import_opt('enable_instance_password',
'nova.api.openstack.compute.servers')
'nova.api.openstack.compute.legacy_v2.servers')
authorize = extensions.os_compute_authorizer(ALIAS)

View File

@ -48,7 +48,7 @@ ALIAS = 'servers'
CONF = cfg.CONF
CONF.import_opt('enable_instance_password',
'nova.api.openstack.compute.servers')
'nova.api.openstack.compute.legacy_v2.servers')
CONF.import_opt('network_api_class', 'nova.network')
CONF.import_opt('reclaim_instance_interval', 'nova.compute.manager')
CONF.import_opt('extensions_blacklist', 'nova.api.openstack', group='osapi_v3')

View File

@ -103,13 +103,3 @@ class Versions(wsgi.Resource):
args['action'] = 'multi'
return args
class VersionV2(object):
def show(self, req):
builder = views_versions.get_view_builder(req)
return builder.build_version(VERSIONS['v2.0'])
def create_resource():
return wsgi.Resource(VersionV2())

View File

@ -24,9 +24,9 @@ import nova.api.openstack.compute
import nova.api.openstack.compute.contrib
import nova.api.openstack.compute.contrib.fping
import nova.api.openstack.compute.contrib.os_tenant_networks
import nova.api.openstack.compute.extensions
import nova.api.openstack.compute.legacy_v2.extensions
import nova.api.openstack.compute.legacy_v2.servers
import nova.api.openstack.compute.plugins.v3.hide_server_addresses
import nova.api.openstack.compute.servers
import nova.availability_zones
import nova.baserpc
import nova.cells.manager
@ -138,9 +138,9 @@ def list_opts():
nova.api.openstack.compute.contrib.fping.fping_opts,
nova.api.openstack.compute.contrib.os_tenant_networks.
os_network_opts,
nova.api.openstack.compute.extensions.ext_opts,
nova.api.openstack.compute.legacy_v2.extensions.ext_opts,
nova.api.openstack.compute.plugins.v3.hide_server_addresses.opts,
nova.api.openstack.compute.servers.server_opts,
nova.api.openstack.compute.legacy_v2.servers.server_opts,
)),
('neutron', nova.api.metadata.handler.metadata_proxy_opts),
('osapi_v3', nova.api.openstack.api_opts),

View File

@ -41,7 +41,7 @@ CONF.import_opt('shelved_offload_time', 'nova.compute.manager')
CONF.import_opt('enable_network_quota',
'nova.api.openstack.compute.contrib.os_tenant_networks')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
CONF.import_opt('osapi_compute_link_prefix', 'nova.api.openstack.common')
CONF.import_opt('osapi_glance_link_prefix', 'nova.api.openstack.common')
CONF.import_opt('enable', 'nova.cells.opts', group='cells')

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AccessIPsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AdminActionsSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AdminPasswordJsonTest(test_servers.ServersSampleBase):

View File

@ -21,7 +21,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AgentsJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AggregatesSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.api.openstack import fakes
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AssistedVolumeSnapshotsJsonTests(test_servers.ServersSampleBase):

View File

@ -23,7 +23,7 @@ from nova.tests.unit import fake_network_cache_model
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AttachInterfacesSampleJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('manager', 'nova.cells.opts', group='cells')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class AvailabilityZoneJsonTest(test_servers.ServersSampleBase):

View File

@ -20,7 +20,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FakeNode(object):

View File

@ -25,7 +25,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class CellsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class CertificatesSamplesJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -25,7 +25,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('vpn_image_id', 'nova.cloudpipe.pipelib')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class CloudPipeSampleTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -21,7 +21,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ConfigDriveSampleJsonTest(test_servers.ServersSampleBase):

View File

@ -21,7 +21,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ConsoleAuthTokensSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ConsoleOutputSampleJsonTest(test_servers.ServersSampleBase):

View File

@ -22,7 +22,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class CreateBackupSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class DeferredDeleteSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class DiskConfigJsonTest(test_servers.ServersSampleBase):

View File

@ -23,7 +23,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class EvacuateJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ExtendedAvailabilityZoneJsonTests(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ExtendedServerAttributesJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ExtendedStatusSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -24,7 +24,7 @@ from nova.tests.unit import fake_instance
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ExtendedVolumesSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -21,7 +21,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
def fake_soft_extension_authorizer(extension_name, core=False):

View File

@ -22,7 +22,7 @@ from nova.tests.unit import utils as test_utils
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FixedIpTest(test_servers.ServersSampleBase):

View File

@ -18,7 +18,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FlavorAccessSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FlavorExtraSpecsSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FlavorManageSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FlavorRxtxJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FlavorsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -18,7 +18,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FloatingIpDNSTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FloatingIPPoolsSampleTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -22,7 +22,7 @@ CONF = cfg.CONF
CONF.import_opt('default_floating_pool', 'nova.network.floating_ips')
CONF.import_opt('public_interface', 'nova.network.linux_net')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FloatingIpsTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -22,7 +22,7 @@ CONF = cfg.CONF
CONF.import_opt('default_floating_pool', 'nova.network.floating_ips')
CONF.import_opt('public_interface', 'nova.network.linux_net')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FloatingIpsBulkTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -23,7 +23,7 @@ from nova import utils
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class FpingSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -22,7 +22,7 @@ CONF = cfg.CONF
CONF.import_opt('osapi_hide_server_address_states',
'nova.api.openstack.compute.plugins.v3.hide_server_addresses')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServersSampleHideAddressesJsonTest(test_servers.ServersSampleJsonTest):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class HostsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -24,7 +24,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class HypervisorsSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ImageSizeSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ImagesSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -27,7 +27,7 @@ from nova.tests.unit import utils as test_utils
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -21,7 +21,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class InstanceUsageAuditLogJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -23,7 +23,7 @@ from nova.tests.unit import fake_crypto
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class KeyPairsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class LockServerSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -23,7 +23,7 @@ from nova import utils
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class MigrateServerSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -22,7 +22,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class MigrationsSamplesJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class MultinicSampleJsonTest(test_servers.ServersSampleBase):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class MultipleCreateJsonTest(test_servers.ServersSampleBase):

View File

@ -21,7 +21,7 @@ from nova.tests.unit.api.openstack.compute.contrib import test_networks
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class NetworksJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -20,7 +20,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class NetworksAssociateJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class PauseServerSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class PreserveEphemeralOnRebuildJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class QuotaClassesSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class QuotaSetsSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ConsolesSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -20,7 +20,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class RescueJsonTest(test_servers.ServersSampleBase):

View File

@ -22,7 +22,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class SchedulerHintsJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -18,7 +18,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class SecurityGroupDefaultRulesSampleJsonTest(

View File

@ -20,7 +20,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
def fake_get(*args, **kwargs):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServerDiagnosticsSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -18,7 +18,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServerExternalEventsSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import api_sample_base
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServerGroupsSampleJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServersMetadataJsonTest(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServerPasswordSampleJsonTests(test_servers.ServersSampleBase):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServerUsageSampleJsonTest(test_servers.ServersSampleBase):

View File

@ -20,7 +20,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServersSampleBase(api_sample_base.ApiSampleTestBaseV3):

View File

@ -19,7 +19,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServersIpsJsonTest(test_servers.ServersSampleBase):

View File

@ -22,7 +22,7 @@ from nova.tests.unit.api.openstack.compute.contrib import test_services
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -20,7 +20,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('shelved_offload_time', 'nova.compute.manager')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class ShelveJsonTest(test_servers.ServersSampleBase):

View File

@ -22,7 +22,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class SimpleTenantUsageSampleJsonTest(test_servers.ServersSampleBase):

View File

@ -18,7 +18,7 @@ from nova.tests.functional.v3 import test_servers
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class SuspendServerSamplesJsonTest(test_servers.ServersSampleBase):

View File

@ -24,7 +24,7 @@ CONF = cfg.CONF
CONF.import_opt('enable_network_quota',
'nova.api.openstack.compute.contrib.os_tenant_networks')
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class TenantNetworksJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -21,7 +21,7 @@ from nova.tests.unit.image import fake
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class UserDataJsonTest(api_sample_base.ApiSampleTestBaseV3):

View File

@ -31,7 +31,7 @@ from nova.volume import cinder
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
'nova.api.openstack.compute.extensions')
'nova.api.openstack.compute.legacy_v2.extensions')
class SnapshotsSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):

View File

@ -14,10 +14,10 @@
import webob
from nova.api.openstack.compute.legacy_v2 import servers as servers_v20
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import access_ips
from nova.api.openstack.compute.plugins.v3 import servers as servers_v21
from nova.api.openstack.compute import servers as servers_v20
from nova.api.openstack import extensions as extensions_v20
from nova.api.openstack import wsgi
from nova.compute import api as compute_api

View File

@ -16,9 +16,9 @@
import mock
import webob
from nova.api.openstack.compute.legacy_v2 import servers
from nova.api.openstack.compute.plugins.v3 import admin_password \
as admin_password_v21
from nova.api.openstack.compute import servers
as admin_password_v21
from nova.compute import api as compute_api
from nova import exception
from nova import test

View File

@ -17,10 +17,10 @@ import datetime
import iso8601
from nova.api.openstack.compute.contrib import availability_zone as az_v2
from nova.api.openstack.compute.legacy_v2 import servers as servers_v2
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import availability_zone as az_v21
from nova.api.openstack.compute.plugins.v3 import servers as servers_v21
from nova.api.openstack.compute import servers as servers_v2
from nova.api.openstack import extensions
from nova import availability_zones
from nova.compute import api as compute_api

View File

@ -20,11 +20,11 @@ from oslo_serialization import jsonutils
from six.moves import range
from webob import exc
from nova.api.openstack.compute import extensions
from nova.api.openstack.compute.legacy_v2 import extensions
from nova.api.openstack.compute.legacy_v2 import servers as servers_v2
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import block_device_mapping
from nova.api.openstack.compute.plugins.v3 import servers as servers_v21
from nova.api.openstack.compute import servers as servers_v2
from nova import block_device
from nova.compute import api as compute_api
from nova import exception

View File

@ -19,12 +19,12 @@ from oslo_config import cfg
from oslo_serialization import jsonutils
from webob import exc
from nova.api.openstack.compute import extensions
from nova.api.openstack.compute.legacy_v2 import extensions
from nova.api.openstack.compute.legacy_v2 import servers as servers_v2
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import block_device_mapping_v1 as \
block_device_mapping
from nova.api.openstack.compute.plugins.v3 import servers as servers_v21
from nova.api.openstack.compute import servers as servers_v2
from nova.compute import api as compute_api
from nova import exception
from nova import test

Some files were not shown because too many files have changed in this diff Show More