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:
parent
d4522d649b
commit
a7ca5069fc
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 []
|
|
@ -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())
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue