Remove Block Storage API v2

In this patch:
- adjusted VersionsController to return only v3
- removed cinder.api.v2.router
- adjustments to cinder.tests.unit.api.contrib to use /v3 only
- moved cinder.api.v2.snapshot_metadata (and tests) to cinder.api.v3
- moved cinder.api.v2.types (and view, tests) to cinder.api.v3
- updated versions response in api-ref
- removed unnecessary config option
- updated various sample config files
- removed experimental tempest-cinder-v2-api job
- updated some docs
- updated non-voting rally job config

Some cinder.api.v2 modules are left because the v3 classes depend on
them, but with the v2 router removed, these are unreachable via the
/v2 path.

Depends-on: https://review.opendev.org/c/openstack/rally-openstack/+/794891
(changes rally to use Block Storage API v3)
Depends-on: https://review.opendev.org/c/openstack/requirements/+/794894
(corrects regression in upper-constraint on Sphinx)

Change-Id: I2093d77db9beec7543c7524d2cd273e79dd5fd5d
This commit is contained in:
Brian Rosmaita 2021-05-19 14:57:17 -04:00
parent d5f0e51879
commit e05b261af7
65 changed files with 720 additions and 1155 deletions

View File

@ -91,8 +91,6 @@
irrelevant-files: *gate-irrelevant-files
experimental:
jobs:
- tempest-cinder-v2-api:
irrelevant-files: *gate-irrelevant-files
- legacy-tempest-dsvm-multibackend-matrix:
irrelevant-files: *gate-irrelevant-files
- cinder-grenade-mn-sub-volschbak:

View File

@ -21,7 +21,7 @@
],
"min_version": "3.0",
"status": "CURRENT",
"updated": "2021-02-03T00:00:00Z",
"updated": "2021-05-30T00:00:00Z",
"version": "3.64"
}
]

View File

@ -1,29 +1,5 @@
{
"versions": [
{
"id": "v2.0",
"links": [
{
"href": "https://docs.openstack.org/",
"rel": "describedby",
"type": "text/html"
},
{
"href": "http://127.0.0.1:45697/v2/",
"rel": "self"
}
],
"media-types": [
{
"base": "application/json",
"type": "application/vnd.openstack.volume+json;version=2"
}
],
"min_version": "",
"status": "DEPRECATED",
"updated": "2017-02-25T12:00:00Z",
"version": ""
},
{
"id": "v3.0",
"links": [
@ -45,7 +21,7 @@
],
"min_version": "3.0",
"status": "CURRENT",
"updated": "2021-02-03T00:00:00Z",
"updated": "2021-05-30T00:00:00Z",
"version": "3.64"
}
]

View File

@ -29,15 +29,16 @@ def root_app_factory(loader, global_conf, **local_conf):
# to check for and remove any legacy references to the v1 API
if '/v1' in local_conf:
LOG.warning('The v1 API has been removed and is no longer '
'available. Client applications should now be '
'moving to v3. Ensure enable_v3_api=true in your '
'available. Client applications should be '
'using v3. Ensure enable_v3_api=true in your '
'cinder.conf file.')
del local_conf['/v1']
if CONF.enable_v2_api:
LOG.warning('The v2 API is deprecated and is not under active '
'development. You should set enable_v2_api=false '
'and enable_v3_api=true in your cinder.conf file.')
else:
if '/v2' in local_conf:
LOG.warning('The v2 API has been removed and is no longer available. '
'Client applications must now use the v3 API only. '
'The \'enable_v2_api\' option has been removed and is '
'ignored in the cinder.conf file.')
del local_conf['/v2']
return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)

View File

@ -202,7 +202,7 @@ class VolumeAdminController(AdminController):
@wsgi.response(HTTPStatus.ACCEPTED)
@wsgi.action('os-migrate_volume')
@validation.schema(admin_actions.migrate_volume, mv.V2_BASE_VERSION,
@validation.schema(admin_actions.migrate_volume, mv.BASE_VERSION,
mv.get_prior_version(mv.VOLUME_MIGRATE_CLUSTER))
@validation.schema(admin_actions.migrate_volume_v316,
mv.VOLUME_MIGRATE_CLUSTER)

View File

@ -145,7 +145,7 @@ class BackupsController(wsgi.Controller):
# immediately
# - maybe also do validation of swift container name
@wsgi.response(HTTPStatus.ACCEPTED)
@validation.schema(backup.create, mv.V2_BASE_VERSION,
@validation.schema(backup.create, mv.BASE_VERSION,
mv.get_prior_version(mv.BACKUP_METADATA))
@validation.schema(backup.create_backup_v343, mv.BACKUP_METADATA,
mv.get_prior_version(mv.BACKUP_AZ))

View File

@ -192,7 +192,7 @@ class VolumeActionsController(wsgi.Controller):
@wsgi.response(HTTPStatus.ACCEPTED)
@wsgi.action('os-volume_upload_image')
@validation.schema(volume_action.volume_upload_image, mv.V2_BASE_VERSION,
@validation.schema(volume_action.volume_upload_image, mv.BASE_VERSION,
mv.get_prior_version(mv.UPLOAD_IMAGE_PARAMS))
@validation.schema(volume_action.volume_upload_image_v31,
mv.UPLOAD_IMAGE_PARAMS)

View File

@ -46,7 +46,7 @@ class VolumeManageController(wsgi.Controller):
self._list_manageable_view = list_manageable_view.ViewBuilder()
@wsgi.response(HTTPStatus.ACCEPTED)
@validation.schema(volume_manage.volume_manage_create, mv.V2_BASE_VERSION,
@validation.schema(volume_manage.volume_manage_create, mv.BASE_VERSION,
mv.get_prior_version(mv.VOLUME_MIGRATE_CLUSTER))
@validation.schema(volume_manage.volume_manage_create_v316,
mv.VOLUME_MIGRATE_CLUSTER)

View File

@ -37,8 +37,6 @@ from cinder import exception
# Add new constants here for each new microversion.
V2_BASE_VERSION = '2.0'
BASE_VERSION = '3.0'
UPLOAD_IMAGE_PARAMS = '3.1'

View File

@ -153,11 +153,9 @@ REST_API_VERSION_HISTORY = """
# The minimum and maximum versions of the API supported
# The default api version request is defined to be the
# minimum version of the API supported.
# Explicitly using /v2 endpoints will still work
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.64"
_LEGACY_API_VERSION2 = "2.0"
UPDATED = "2021-02-03T00:00:00Z"
UPDATED = "2021-05-30T00:00:00Z"
# NOTE(cyeoh): min and max versions declared as functions so we can
@ -171,10 +169,6 @@ def max_api_version():
return APIVersionRequest(_MAX_API_VERSION)
def legacy_api_version2():
return APIVersionRequest(_LEGACY_API_VERSION2)
class APIVersionRequest(utils.ComparableMixin):
"""This class represents an API Version Request.

View File

@ -275,16 +275,9 @@ class Request(webob.Request):
return self.accept_language.best_match(all_languages)
def set_api_version_request(self, url):
"""Set API version request based on the request header information.
"""Set API version request based on the request header information."""
Microversions starts with /v3, so if a client sends a request for
version 1.0 or 2.0 with the /v3 endpoint, throw an exception.
Sending a header with any microversion to a /v2 endpoint will
be ignored.
Note that a microversion must be set for the legacy endpoint. This
will appear as 2.0 for /v2.
"""
if API_VERSION_REQUEST_HEADER in self.headers and 'v3' in url:
if API_VERSION_REQUEST_HEADER in self.headers:
hdr_string = self.headers[API_VERSION_REQUEST_HEADER]
# 'latest' is a special keyword which is equivalent to requesting
# the maximum version of the API supported
@ -314,11 +307,8 @@ class Request(webob.Request):
max_ver=api_version.max_api_version().get_string())
else:
if 'v2' in url:
self.api_version_request = api_version.legacy_api_version2()
else:
self.api_version_request = api_version.APIVersionRequest(
api_version._MIN_API_VERSION)
self.api_version_request = api_version.APIVersionRequest(
api_version._MIN_API_VERSION)
class ActionDispatcher(object):

View File

@ -1,93 +0,0 @@
# Copyright 2011 OpenStack Foundation
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
"""
WSGI middleware for OpenStack Volume API.
"""
from cinder.api import extensions
import cinder.api.openstack
from cinder.api.v2 import limits
from cinder.api.v2 import snapshot_metadata
from cinder.api.v2 import snapshots
from cinder.api.v2 import types
from cinder.api.v2 import volume_metadata
from cinder.api.v2 import volumes
from cinder.api import versions
class APIRouter(cinder.api.openstack.APIRouter):
"""Routes requests on the API to the appropriate controller and method."""
ExtensionManager = extensions.ExtensionManager
def _setup_routes(self, mapper, ext_mgr):
self.resources['versions'] = versions.create_resource()
mapper.connect("versions", "/",
controller=self.resources['versions'],
action='index')
mapper.redirect("", "/")
self.resources['volumes'] = volumes.create_resource(ext_mgr)
mapper.resource("volume", "volumes",
controller=self.resources['volumes'],
collection={'detail': 'GET'},
member={'action': 'POST'})
self.resources['types'] = types.create_resource()
mapper.resource("type", "types",
controller=self.resources['types'],
member={'action': 'POST'})
self.resources['snapshots'] = snapshots.create_resource(ext_mgr)
mapper.resource("snapshot", "snapshots",
controller=self.resources['snapshots'],
collection={'detail': 'GET'},
member={'action': 'POST'})
self.resources['limits'] = limits.create_resource()
mapper.resource("limit", "limits",
controller=self.resources['limits'])
self.resources['snapshot_metadata'] = \
snapshot_metadata.create_resource()
snapshot_metadata_controller = self.resources['snapshot_metadata']
mapper.resource("snapshot_metadata", "metadata",
controller=snapshot_metadata_controller,
parent_resource=dict(member_name='snapshot',
collection_name='snapshots'))
mapper.connect("metadata",
"/{project_id}/snapshots/{snapshot_id}/metadata",
controller=snapshot_metadata_controller,
action='update_all',
conditions={"method": ['PUT']})
self.resources['volume_metadata'] = volume_metadata.create_resource()
volume_metadata_controller = self.resources['volume_metadata']
mapper.resource("volume_metadata", "metadata",
controller=volume_metadata_controller,
parent_resource=dict(member_name='volume',
collection_name='volumes'))
mapper.connect("metadata",
"/{project_id}/volumes/{volume_id}/metadata",
controller=volume_metadata_controller,
action='update_all',
conditions={"method": ['PUT']})

View File

@ -177,8 +177,14 @@ class VolumeController(wsgi.Controller):
"access requested image.")
raise exc.HTTPBadRequest(explanation=msg)
# NOTE: using mv.BASE_VERSION (which is 3.0) is a bit nonstandard,
# but this class is no longer consumed by the v2 API, though it is
# a superclass of cinder.api.v3.volumes. Although create() is
# overridden in the subclass, I didn't want to remove it from
# here until we are sure that the v3 unit tests for create() test
# everything that the v2 unit tests covered.
@wsgi.response(HTTPStatus.ACCEPTED)
@validation.schema(volumes.create, mv.V2_BASE_VERSION)
@validation.schema(volumes.create, mv.BASE_VERSION)
def create(self, req, body):
"""Creates a new volume."""
@ -279,7 +285,8 @@ class VolumeController(wsgi.Controller):
"""Return volume search options allowed by non-admin."""
return common.get_enabled_resource_filters('volume').get('volume', [])
@validation.schema(volumes.update, mv.V2_BASE_VERSION,
# NOTE: see NOTE for create(), above
@validation.schema(volumes.update, mv.BASE_VERSION,
mv.get_prior_version(mv.SUPPORT_VOLUME_SCHEMA_CHANGES))
@validation.schema(volumes.update_volume_v353,
mv.SUPPORT_VOLUME_SCHEMA_CHANGES)

View File

@ -21,8 +21,6 @@ WSGI middleware for OpenStack Volume API.
from cinder.api import extensions
import cinder.api.openstack
from cinder.api.v2 import snapshot_metadata
from cinder.api.v2 import types
from cinder.api.v3 import attachments
from cinder.api.v3 import backups
from cinder.api.v3 import clusters
@ -36,7 +34,9 @@ from cinder.api.v3 import limits
from cinder.api.v3 import messages
from cinder.api.v3 import resource_filters
from cinder.api.v3 import snapshot_manage
from cinder.api.v3 import snapshot_metadata
from cinder.api.v3 import snapshots
from cinder.api.v3 import types
from cinder.api.v3 import volume_manage
from cinder.api.v3 import volume_metadata
from cinder.api.v3 import volume_transfer

View File

@ -23,7 +23,7 @@ from cinder.api import api_utils
from cinder.api import common
from cinder.api import microversions as mv
from cinder.api.openstack import wsgi
from cinder.api.v2.views import types as views_types
from cinder.api.v3.views import types as views_types
from cinder import exception
from cinder.i18n import _
from cinder.policies import volume_type as type_policy

View File

@ -147,7 +147,8 @@ class VolumeController(volumes_v2.VolumeController):
self._process_volume_filtering(context=context, filters=filters,
req_version=req_version)
# NOTE(thingee): v2 API allows name instead of display_name
# NOTE: it's 'name' in the REST API, but 'display_name' in the
# database layer, so we need to do this translation
if 'name' in sort_keys:
sort_keys[sort_keys.index('name')] = 'display_name'
@ -302,12 +303,11 @@ class VolumeController(volumes_v2.VolumeController):
kwargs = {}
self.validate_name_and_description(volume, check_length=False)
# NOTE(thingee): v2 API allows name instead of display_name
# NOTE: it's 'name'/'description' in the REST API, but
# 'display_name'/display_description' in the database layer,
# so we need to do this translation
if 'name' in volume:
volume['display_name'] = volume.pop('name')
# NOTE(thingee): v2 API allows description instead of
# display_description
if 'description' in volume:
volume['display_description'] = volume.pop('description')

View File

@ -38,18 +38,6 @@ _LINKS = [{
_KNOWN_VERSIONS = {
"v2.0": {
"id": "v2.0",
"status": "DEPRECATED",
"version": "",
"min_version": "",
"updated": "2017-02-25T12:00:00Z",
"links": _LINKS,
"media-types": [{
"base": "application/json",
"type": "application/vnd.openstack.volume+json;version=2",
}]
},
"v3.0": {
"id": "v3.0",
"status": "CURRENT",
@ -93,24 +81,15 @@ class VersionsController(wsgi.Controller):
def __init__(self):
super(VersionsController, self).__init__(None)
@wsgi.Controller.api_version('2.0')
def index(self, req): # pylint: disable=E0102
"""Return versions supported prior to the microversions epoch."""
builder = views_versions.get_view_builder(req)
known_versions = copy.deepcopy(_KNOWN_VERSIONS)
known_versions.pop('v3.0')
return builder.build_versions(known_versions)
@index.api_version('3.0')
@wsgi.Controller.api_version('3.0')
def index(self, req): # pylint: disable=E0102
"""Return versions supported after the start of microversions."""
builder = views_versions.get_view_builder(req)
known_versions = copy.deepcopy(_KNOWN_VERSIONS)
known_versions.pop('v2.0')
return builder.build_versions(known_versions)
# NOTE (cknight): Calling the versions API without
# /v2 or /v3 in the URL will lead to this unversioned
# /v3 in the URL will lead to this unversioned
# method, which should always return info about all
# available versions.
@wsgi.response(HTTPStatus.MULTIPLE_CHOICES)
@ -119,8 +98,8 @@ class VersionsController(wsgi.Controller):
builder = views_versions.get_view_builder(req)
known_versions = copy.deepcopy(_KNOWN_VERSIONS)
if not CONF.enable_v2_api:
known_versions.pop('v2.0')
# FIXME: remove this in Y ... I suppose we should honor
# it in Xena, even though it doesn't make any sense
if not CONF.enable_v3_api:
known_versions.pop('v3.0')

View File

@ -47,12 +47,12 @@ core_opts = [
CONF.register_cli_opts(core_opts)
api_opts = [
cfg.BoolOpt('enable_v2_api',
default=True,
deprecated_for_removal=True,
help="DEPRECATED: Deploy v2 of the Cinder API."),
cfg.BoolOpt('enable_v3_api',
default=True,
deprecated_for_removal=True,
deprecated_reason=('This is the only API version available, '
'so disabling it is not an option.'),
deprecated_since="Xena",
help="Deploy v3 of the Cinder API."),
cfg.BoolOpt('api_rate_limit',
default=True,

View File

@ -1,29 +1,5 @@
{
"versions": [
{
"status": "DEPRECATED",
"updated": "%(isotime)s",
"links": [
{
"href": "https://docs.openstack.org/",
"type": "text/html",
"rel": "describedby"
},
{
"href": "%(host)s/v2/",
"rel": "self"
}
],
"min_version": "",
"version": "",
"media-types": [
{
"base": "application/json",
"type": "application/vnd.openstack.volume+json;version=2"
}
],
"id": "v2.0"
},
{
"status": "CURRENT",
"updated": "%(isotime)s",

View File

@ -35,7 +35,7 @@ from cinder.objects import base as obj_base
from cinder.objects import fields
from cinder.scheduler import rpcapi as scheduler_rpcapi
from cinder.tests.unit.api import fakes
from cinder.tests.unit.api.v2 import fakes as v2_fakes
from cinder.tests.unit.api.v3 import fakes as v3_fakes
from cinder.tests.unit import cast_as_call
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_snapshot
@ -48,14 +48,7 @@ from cinder.volume import volume_types
def app():
# no auth, just let environ['cinder.context'] pass through
api = fakes.router.APIRouter()
mapper = fakes.urlmap.URLMap()
mapper['/v2'] = api
return mapper
def app_v3():
api = fakes.router.APIRouter()
api = fakes.router_v3.APIRouter()
mapper = fakes.urlmap.URLMap()
mapper['/v3'] = api
return mapper
@ -123,7 +116,7 @@ class AdminActionsTest(BaseAdminTest):
super(AdminActionsTest, self).tearDown()
def _issue_resource_reset(self, ctx, name, id, status):
req = webob.Request.blank('/v2/%s/%s/%s/action' % (
req = webob.Request.blank('/v3/%s/%s/%s/action' % (
fake.PROJECT_ID, name, id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -370,7 +363,7 @@ class AdminActionsTest(BaseAdminTest):
'user_id': fake.USER_ID,
'project_id': fake.PROJECT_ID})
req = webob.Request.blank('/v2/%s/%s/%s/action' % (
req = webob.Request.blank('/v3/%s/%s/%s/action' % (
fake.PROJECT_ID, 'backups', backup['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -405,7 +398,7 @@ class AdminActionsTest(BaseAdminTest):
self.assertEqual('available', volume['status'])
def test_reset_status_for_missing_volume(self):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -541,7 +534,7 @@ class AdminActionsTest(BaseAdminTest):
snapshot.create()
self.addCleanup(snapshot.destroy)
req = webob.Request.blank('/v2/%s/%s/%s/action' % (
req = webob.Request.blank('/v3/%s/%s/%s/action' % (
fake.PROJECT_ID, 'snapshots', snapshot['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -553,7 +546,7 @@ class AdminActionsTest(BaseAdminTest):
def test_force_delete(self):
# current status is creating
volume = self._create_volume(self.ctx, {'size': 1, 'host': None})
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -573,14 +566,14 @@ class AdminActionsTest(BaseAdminTest):
@mock.patch.object(db, 'volume_get')
def test_force_delete_snapshot(self, volume_get, snapshot_get, get_by_id,
delete_snapshot):
volume = v2_fakes.create_fake_volume(fake.VOLUME_ID)
snapshot = v2_fakes.fake_snapshot(fake.SNAPSHOT_ID)
volume = v3_fakes.create_volume(fake.VOLUME_ID)
snapshot = v3_fakes.fake_snapshot(fake.SNAPSHOT_ID)
snapshot_obj = fake_snapshot.fake_snapshot_obj(self.ctx, **snapshot)
volume_get.return_value = volume
snapshot_get.return_value = snapshot
get_by_id.return_value = snapshot_obj
path = '/v2/%s/snapshots/%s/action' % (
path = '/v3/%s/snapshots/%s/action' % (
fake.PROJECT_ID, snapshot['id'])
req = webob.Request.blank(path)
req.method = 'POST'
@ -633,7 +626,7 @@ class AdminActionsTest(BaseAdminTest):
body['os-migrate_volume']['cluster'] = cluster
req.body = jsonutils.dump_as_bytes(body)
req.environ['cinder.context'] = ctx
resp = req.get_response(app_v3())
resp = req.get_response(app())
# verify status
self.assertEqual(expected_status, resp.status_int)
@ -676,7 +669,7 @@ class AdminActionsTest(BaseAdminTest):
def _migrate_volume_exec(self, ctx, volume, host, expected_status,
force_host_copy=False, lock_volume=False):
# build request to migrate to host
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -737,7 +730,7 @@ class AdminActionsTest(BaseAdminTest):
host = 'test3'
volume = self._migrate_volume_prep()
# build request to migrate without host
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -804,7 +797,7 @@ class AdminActionsTest(BaseAdminTest):
def _migrate_volume_comp_exec(self, ctx, volume, new_volume, error,
expected_status, expected_id, no_body=False):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -893,7 +886,7 @@ class AdminActionsTest(BaseAdminTest):
volume = db.volume_create(self.ctx, {'id': fake.VOLUME_ID,
'volume_type_id':
fake.VOLUME_TYPE_ID})
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume['id']))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -920,7 +913,7 @@ class AdminActionsTest(BaseAdminTest):
backup = test_utils.create_backup(self.ctx, status=test_status,
size=1, availability_zone='az1',
host='testhost')
req = webob.Request.blank('/v2/%s/backups/%s/action' % (
req = webob.Request.blank('/v3/%s/backups/%s/action' % (
fake.PROJECT_ID, backup.id))
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
@ -957,7 +950,7 @@ class AdminActionsTest(BaseAdminTest):
# admin context
self.override_config('backup_driver', 'cinder.backup.drivers.ceph')
backup = test_utils.create_backup(self.ctx, size=1)
req = webob.Request.blank('/v2/%s/backups/%s/action' % (
req = webob.Request.blank('/v3/%s/backups/%s/action' % (
fake.PROJECT_ID, backup.id))
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
@ -1003,7 +996,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
connector)
self.assertEqual('rw', conn_info['data']['access_mode'])
# build request to force detach
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -1055,7 +1048,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
volume, connector)
self.assertEqual('ro', conn_info['data']['access_mode'])
# build request to force detach
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -1109,7 +1102,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
messaging.RemoteError(exc_type='VolumeAttachmentNotFound')
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -1126,7 +1119,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
messaging.RemoteError(exc_type='VolumeBackendAPIException'))
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -1172,7 +1165,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
volume_remote_error = messaging.RemoteError(exc_type='DBError')
with mock.patch.object(volume_api.API, 'detach',
side_effect=volume_remote_error):
req = webob.Request.blank('/v2/%s/volumes/%s/action' %
req = webob.Request.blank('/v3/%s/volumes/%s/action' %
(fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'
@ -1214,7 +1207,7 @@ class AdminActionsAttachDetachTest(BaseAdminTest):
# test when missing connector
with mock.patch.object(volume_api.API, 'detach'):
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
req = webob.Request.blank('/v3/%s/volumes/%s/action' % (
fake.PROJECT_ID, volume.id))
req.method = 'POST'
req.headers['content-type'] = 'application/json'

View File

@ -82,7 +82,7 @@ class BackupsAPITestCase(test.TestCase):
container='volumebackups',
size=1,
availability_zone='az1')
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, backup.id))
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -129,7 +129,7 @@ class BackupsAPITestCase(test.TestCase):
backup.destroy()
def test_show_backup_with_backup_NotFound(self):
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
req.method = 'GET'
req.headers = mv.get_mv_header(mv.BACKUP_METADATA)
@ -150,7 +150,7 @@ class BackupsAPITestCase(test.TestCase):
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
res = req.get_response(fakes.wsgi_app(
@ -177,7 +177,7 @@ class BackupsAPITestCase(test.TestCase):
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
req = webob.Request.blank('/v2/%s/backups?limit=2' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups?limit=2' % fake.PROJECT_ID)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
res = req.get_response(fakes.wsgi_app(
@ -198,7 +198,7 @@ class BackupsAPITestCase(test.TestCase):
backup1.destroy()
def test_list_backups_with_offset_out_of_range(self):
url = '/v2/%s/backups?offset=252452434242342434' % fake.PROJECT_ID
url = '/v3/%s/backups?offset=252452434242342434' % fake.PROJECT_ID
req = webob.Request.blank(url)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -210,7 +210,7 @@ class BackupsAPITestCase(test.TestCase):
backup1 = utils.create_backup(self.context)
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
url = '/v2/%s/backups?marker=%s' % (fake.PROJECT_ID, backup3.id)
url = '/v3/%s/backups?marker=%s' % (fake.PROJECT_ID, backup3.id)
req = webob.Request.blank(url)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -236,7 +236,7 @@ class BackupsAPITestCase(test.TestCase):
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
url = ('/v2/%s/backups?limit=1&marker=%s' % (fake.PROJECT_ID,
url = ('/v3/%s/backups?limit=1&marker=%s' % (fake.PROJECT_ID,
backup3.id))
req = webob.Request.blank(url)
req.method = 'GET'
@ -264,7 +264,7 @@ class BackupsAPITestCase(test.TestCase):
backup3 = utils.create_backup(self.context, availability_zone='az1',
container='volumebackups', size=1)
req = webob.Request.blank('/v2/%s/backups/detail' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups/detail' % fake.PROJECT_ID)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
@ -302,7 +302,7 @@ class BackupsAPITestCase(test.TestCase):
backup3 = utils.create_backup(self.context, availability_zone='az1',
container='volumebackups', size=1)
req = webob.Request.blank('/v2/%s/backups/detail' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups/detail' % fake.PROJECT_ID)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
@ -395,7 +395,7 @@ class BackupsAPITestCase(test.TestCase):
status=fields.BackupStatus.AVAILABLE)
backup3 = utils.create_backup(self.context, volume_id=fake.VOLUME3_ID)
req = webob.Request.blank('/v2/%s/backups/detail?name=test2' %
req = webob.Request.blank('/v3/%s/backups/detail?name=test2' %
fake.PROJECT_ID)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -408,7 +408,7 @@ class BackupsAPITestCase(test.TestCase):
self.assertEqual(HTTPStatus.OK, res.status_int)
self.assertEqual(backup1.id, res_dict['backups'][0]['id'])
req = webob.Request.blank('/v2/%s/backups/detail?status=available' %
req = webob.Request.blank('/v3/%s/backups/detail?status=available' %
fake.PROJECT_ID)
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -421,7 +421,7 @@ class BackupsAPITestCase(test.TestCase):
self.assertEqual(HTTPStatus.OK, res.status_int)
self.assertEqual(backup2.id, res_dict['backups'][0]['id'])
req = webob.Request.blank('/v2/%s/backups/detail?volume_id=%s' % (
req = webob.Request.blank('/v3/%s/backups/detail?volume_id=%s' % (
fake.PROJECT_ID, fake.VOLUME3_ID))
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -442,7 +442,7 @@ class BackupsAPITestCase(test.TestCase):
backup1 = utils.create_backup(self.context)
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
url = ('/v2/%s/backups/detail?limit=2&sort_key=created_at'
url = ('/v3/%s/backups/detail?limit=2&sort_key=created_at'
'&sort_dir=desc' % fake.PROJECT_ID)
req = webob.Request.blank(url)
req.method = 'GET'
@ -467,7 +467,7 @@ class BackupsAPITestCase(test.TestCase):
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
url = ('/v2/%s/backups/detail?marker=%s' % (
url = ('/v3/%s/backups/detail?marker=%s' % (
fake.PROJECT_ID, backup3.id))
req = webob.Request.blank(url)
req.method = 'GET'
@ -492,7 +492,7 @@ class BackupsAPITestCase(test.TestCase):
backup2 = utils.create_backup(self.context)
backup3 = utils.create_backup(self.context)
url = ('/v2/%s/backups/detail?limit=1&marker=%s' % (
url = ('/v3/%s/backups/detail?limit=1&marker=%s' % (
fake.PROJECT_ID, backup3.id))
req = webob.Request.blank(url)
req.method = 'GET'
@ -511,7 +511,7 @@ class BackupsAPITestCase(test.TestCase):
backup1.destroy()
def test_list_backups_detail_with_offset_out_of_range(self):
url = ('/v2/%s/backups/detail?offset=234534543657634523' %
url = ('/v3/%s/backups/detail?offset=234534543657634523' %
fake.PROJECT_ID)
req = webob.Request.blank(url)
req.method = 'GET'
@ -530,7 +530,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -560,7 +560,7 @@ class BackupsAPITestCase(test.TestCase):
volume = utils.create_volume(self.context, size=5)
body['backup']['volume_id'] = volume.id
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -570,7 +570,7 @@ class BackupsAPITestCase(test.TestCase):
# create backup call doesn't return 'description' in response so get
# the created backup to assert name and description
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, res_dict['backup']['id']))
req.method = 'GET'
req.headers['Content-Type'] = 'application/json'
@ -668,7 +668,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -698,7 +698,7 @@ class BackupsAPITestCase(test.TestCase):
"force": True,
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -723,7 +723,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -750,7 +750,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -777,7 +777,7 @@ class BackupsAPITestCase(test.TestCase):
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -802,7 +802,7 @@ class BackupsAPITestCase(test.TestCase):
"volume_id": volume.id,
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -825,11 +825,10 @@ class BackupsAPITestCase(test.TestCase):
"container": "a" * 256
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.environ['cinder.context'] = self.context
req.api_version_request = api_version.APIVersionRequest()
req.api_version_request = api_version.APIVersionRequest("2.0")
req.api_version_request = api_version.APIVersionRequest("3.0")
self.assertRaises(exception.ValidationError,
self.controller.create,
req,
@ -868,7 +867,7 @@ class BackupsAPITestCase(test.TestCase):
size=1, availability_zone='az1',
host='testhost')
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -905,7 +904,7 @@ class BackupsAPITestCase(test.TestCase):
"incremental": True,
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -924,7 +923,7 @@ class BackupsAPITestCase(test.TestCase):
def test_create_backup_with_no_body(self):
# omit body from the request
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.body = jsonutils.dump_as_bytes(None)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
@ -947,7 +946,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -969,7 +968,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -992,7 +991,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -1013,7 +1012,7 @@ class BackupsAPITestCase(test.TestCase):
"container": "nightlybackups",
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -1033,7 +1032,7 @@ class BackupsAPITestCase(test.TestCase):
_mock_service_get_all.return_value = []
volume = utils.create_volume(self.context, size=2)
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
body = {"backup": {"name": "nightly001",
"description":
"Nightly Backup 03-Sep-2012",
@ -1069,7 +1068,7 @@ class BackupsAPITestCase(test.TestCase):
"incremental": True,
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -1095,7 +1094,7 @@ class BackupsAPITestCase(test.TestCase):
"snapshot_id": None,
}
}
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
req = webob.Request.blank('/v3/%s/backups' % fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
@ -1265,7 +1264,7 @@ class BackupsAPITestCase(test.TestCase):
backup = utils.create_backup(self.context,
status=fields.BackupStatus.AVAILABLE,
availability_zone='az1', host='testhost')
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, backup.id))
req.method = 'DELETE'
req.headers['Content-Type'] = 'application/json'
@ -1293,7 +1292,7 @@ class BackupsAPITestCase(test.TestCase):
status=fields.BackupStatus.AVAILABLE,
incremental=True,
availability_zone='az1', host='testhost')
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, delta.id))
req.method = 'DELETE'
req.headers['Content-Type'] = 'application/json'
@ -1318,7 +1317,7 @@ class BackupsAPITestCase(test.TestCase):
backup = utils.create_backup(self.context,
status=fields.BackupStatus.ERROR,
availability_zone='az1', host='testhost')
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, backup.id))
req.method = 'DELETE'
req.headers['Content-Type'] = 'application/json'
@ -1333,7 +1332,7 @@ class BackupsAPITestCase(test.TestCase):
backup.destroy()
def test_delete_backup_with_backup_NotFound(self):
req = webob.Request.blank('/v2/%s/backups/%s' % (
req = webob.Request.blank('/v3/%s/backups/%s' % (
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
req.method = 'DELETE