Merge "Make project_id optional in v2.1 urls"

This commit is contained in:
Jenkins
2016-01-25 02:42:36 +00:00
committed by Gerrit Code Review
17 changed files with 149 additions and 40 deletions

View File

@@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.17",
"version": "2.18",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.17",
"version": "2.18",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@@ -60,7 +60,13 @@ api_opts = [
'list. Specify the extension aliases here. '
'This option will be removed in the near future. '
'After that point you have to run all of the API.',
deprecated_for_removal=True, deprecated_group='osapi_v21')
deprecated_for_removal=True, deprecated_group='osapi_v21'),
cfg.StrOpt('project_id_regex',
default=None,
help='DEPRECATED: The validation regex for project_ids '
'used in urls. This defaults to [0-9a-f\-]+ if not set, '
'which matches normal uuids created by keystone.',
deprecated_for_removal=True, deprecated_group='osapi_v21')
]
api_opts_group = cfg.OptGroup(name='osapi_v21', title='API v2.1 Options')
@@ -196,14 +202,40 @@ class APIMapper(routes.Mapper):
class ProjectMapper(APIMapper):
def resource(self, member_name, collection_name, **kwargs):
# NOTE(sdague): project_id parameter is only valid if its hex
# or hex + dashes (note, integers are a subset of this). This
# is required to hand our overlaping routes issues.
project_id_regex = '[0-9a-f\-]+'
if CONF.osapi_v21.project_id_regex:
project_id_regex = CONF.osapi_v21.project_id_regex
project_id_token = '{project_id:%s}' % project_id_regex
if 'parent_resource' not in kwargs:
kwargs['path_prefix'] = '{project_id}/'
kwargs['path_prefix'] = '%s/' % project_id_token
else:
parent_resource = kwargs['parent_resource']
p_collection = parent_resource['collection_name']
p_member = parent_resource['member_name']
kwargs['path_prefix'] = '{project_id}/%s/:%s_id' % (p_collection,
p_member)
kwargs['path_prefix'] = '%s/%s/:%s_id' % (
project_id_token,
p_collection,
p_member)
routes.Mapper.resource(
self,
member_name,
collection_name,
**kwargs)
# while we are in transition mode, create additional routes
# for the resource that do not include project_id.
if 'parent_resource' not in kwargs:
del kwargs['path_prefix']
else:
parent_resource = kwargs['parent_resource']
p_collection = parent_resource['collection_name']
p_member = parent_resource['member_name']
kwargs['path_prefix'] = '%s/:%s_id' % (p_collection,
p_member)
routes.Mapper.resource(self, member_name,
collection_name,
**kwargs)

View File

@@ -59,6 +59,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.15 - Add soft-affinity and soft-anti-affinity policies
* 2.16 - Exposes host_status for servers/detail and servers/{server_id}
* 2.17 - Add trigger_crash_dump to server actions
* 2.18 - Makes project_id optional in v2.1
"""
# The minimum and maximum versions of the API supported
@@ -67,7 +68,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.17"
_MAX_API_VERSION = "2.18"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@@ -74,10 +74,14 @@ class NoAuthMiddleware(NoAuthMiddlewareBase):
return self.base_call(req, True, always_admin=False)
# TODO(johnthetubaguy) this should be removed in the M release
class NoAuthMiddlewareV3(NoAuthMiddlewareBase):
"""Return a fake token if one isn't specified."""
class NoAuthMiddlewareV2_17(NoAuthMiddlewareBase):
"""Return a fake token if one isn't specified.
This provides a version of the middleware which does not add
project_id into server management urls.
"""
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
return self.base_call(req, False)
return self.base_call(req, False, always_admin=False)

View File

@@ -154,3 +154,8 @@ class ImageMetadata(extensions.V21APIExtensionBase):
"/{project_id}/images/{image_id}/metadata",
controller=wsgi_resource,
action='update_all', conditions={"method": ['PUT']})
# Also connect the non project_id route
mapper.connect("metadata",
"/images/{image_id}/metadata",
controller=wsgi_resource,
action='update_all', conditions={"method": ['PUT']})

View File

@@ -191,3 +191,8 @@ class ServerMetadata(extensions.V21APIExtensionBase):
"/{project_id}/servers/{server_id}/metadata",
controller=wsgi_resource,
action='update_all', conditions={"method": ['PUT']})
# Also connect the non project_id routes
mapper.connect("metadata",
"/servers/{server_id}/metadata",
controller=wsgi_resource,
action='update_all', conditions={"method": ['PUT']})

View File

@@ -163,3 +163,7 @@ user documentation.
Add a new API for triggering crash dump in an instance. Different operation
systems in instance may need different configurations to trigger crash dump.
2.18
----
Establishes a set of routes that makes project_id an optional construct in v2.1.

View File

@@ -54,3 +54,14 @@ class ApiPasteLegacyV2Fixture(ApiPasteV21Fixture):
"/v2: openstack_compute_api_v21_legacy_v2_compatible",
"/v2: openstack_compute_api_legacy_v2")
target_file.write(line)
class ApiPasteNoProjectId(ApiPasteV21Fixture):
def _replace_line(self, target_file, line):
line = line.replace(
"paste.filter_factory = nova.api.openstack.auth:"
"NoAuthMiddleware.factory",
"paste.filter_factory = nova.api.openstack.auth:"
"NoAuthMiddlewareV2_17.factory")
target_file.write(line)

View File

@@ -69,6 +69,7 @@ class ApiSampleTestBaseV21(testscenarios.WithScenarios,
sample_dir = None
extra_extensions_to_load = None
_legacy_v2_code = False
_project_id = True
scenarios = [
# test v2 with the v2.1 compatibility stack
@@ -82,7 +83,13 @@ class ApiSampleTestBaseV21(testscenarios.WithScenarios,
'api_major_version': 'v2',
'_legacy_v2_code': True,
'_additional_fixtures': [
api_paste_fixture.ApiPasteLegacyV2Fixture]})
api_paste_fixture.ApiPasteLegacyV2Fixture]}),
# test v2.16 code without project id
('v2_1_noproject_id', {
'api_major_version': 'v2.1',
'_project_id': False,
'_additional_fixtures': [
api_paste_fixture.ApiPasteNoProjectId]})
]
def setUp(self):

View File

@@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.17",
"version": "2.18",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.17",
"version": "2.18",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@@ -250,9 +250,19 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
def _update_links(self, sample_data):
"""Process sample data and update version specific links."""
url_re = self._get_host() + "/v(2\.1|2)"
# replace version urls
url_re = self._get_host() + "/v(2|2\.1)/openstack"
new_url = self._get_host() + "/" + self.api_major_version
if self._project_id:
new_url += "/openstack"
updated_data = re.sub(url_re, new_url, sample_data)
# replace unversioned urls
url_re = self._get_host() + "/openstack"
new_url = self._get_host()
if self._project_id:
new_url += "/openstack"
updated_data = re.sub(url_re, new_url, updated_data)
return updated_data
def _verify_response(self, name, subs, response, exp_code,
@@ -360,13 +370,19 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
def _get_compute_endpoint(self):
# NOTE(sdague): "openstack" is stand in for project_id, it
# should be more generic in future.
return '%s/%s' % (self._get_host(), 'openstack')
if self._project_id:
return '%s/%s' % (self._get_host(), 'openstack')
else:
return self._get_host()
def _get_vers_compute_endpoint(self):
# NOTE(sdague): "openstack" is stand in for project_id, it
# should be more generic in future.
return '%s/%s/%s' % (self._get_host(), self.api_major_version,
'openstack')
if self._project_id:
return '%s/%s/%s' % (self._get_host(), self.api_major_version,
'openstack')
else:
return '%s/%s' % (self._get_host(), self.api_major_version)
def _get_response(self, url, method, body=None, strip_version=False,
api_version=None, headers=None):

View File

@@ -17,6 +17,8 @@
import webob
import webob.dec
import testscenarios
from nova.api import openstack as openstack_api
from nova.api.openstack import auth
from nova.api.openstack import compute
@@ -25,15 +27,29 @@ from nova import test
from nova.tests.unit.api.openstack import fakes
class TestNoAuthMiddlewareV21(test.NoDBTestCase):
class TestNoAuthMiddleware(testscenarios.WithScenarios, test.NoDBTestCase):
scenarios = [
('project_id', {
'expected_url': 'http://localhost/v2.1/user1_project',
'auth_middleware': auth.NoAuthMiddleware}),
('no_project_id', {
'expected_url': 'http://localhost/v2.1',
'auth_middleware': auth.NoAuthMiddlewareV2_17}),
]
def setUp(self):
super(TestNoAuthMiddlewareV21, self).setUp()
super(TestNoAuthMiddleware, self).setUp()
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_networking(self)
self.wsgi_app = fakes.wsgi_app_v21(use_no_auth=True)
self.req_url = '/v2'
self.expected_url = "http://localhost/v2/user1_project"
api_v21 = openstack_api.FaultWrapper(
self.auth_middleware(
compute.APIRouterV21()
)
)
self.wsgi_app = urlmap.URLMap()
self.wsgi_app['/v2.1'] = api_v21
self.req_url = '/v2.1'
def test_authorize_user(self):
req = webob.Request.blank(self.req_url)
@@ -66,16 +82,3 @@ class TestNoAuthMiddlewareV21(test.NoDBTestCase):
self.assertEqual(result.status, '204 No Content')
self.assertNotIn('X-CDN-Management-Url', result.headers)
self.assertNotIn('X-Storage-Url', result.headers)
class TestNoAuthMiddlewareV3(TestNoAuthMiddlewareV21):
def setUp(self):
super(TestNoAuthMiddlewareV3, self).setUp()
api_router = compute.APIRouterV3()
api_v3 = openstack_api.FaultWrapper(auth.NoAuthMiddlewareV3(
api_router))
self.wsgi_app = urlmap.URLMap()
self.wsgi_app['/v3'] = api_v3
self.req_url = '/v3'
self.expected_url = "http://localhost/v3"

View File

@@ -66,7 +66,7 @@ EXP_VERSIONS = {
"v2.1": {
"id": "v2.1",
"status": "CURRENT",
"version": "2.17",
"version": "2.18",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z",
"links": [
@@ -128,7 +128,7 @@ class VersionsTestV20(test.NoDBTestCase):
{
"id": "v2.1",
"status": "CURRENT",
"version": "2.17",
"version": "2.18",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z",
"links": [
@@ -194,7 +194,7 @@ class VersionsTestV20(test.NoDBTestCase):
self._test_get_version_2_detail('/', accept=accept)
def test_get_version_2_versions_invalid(self):
req = webob.Request.blank('/v2/versions/1234')
req = webob.Request.blank('/v2/versions/1234/foo')
req.accept = "application/json"
res = req.get_response(self.wsgi_app)
self.assertEqual(404, res.status_int)
@@ -483,7 +483,7 @@ class VersionsTestV21(test.NoDBTestCase):
self.assertEqual(expected, version)
def test_get_version_21_versions_invalid(self):
req = webob.Request.blank('/v2.1/versions/1234')
req = webob.Request.blank('/v2.1/versions/1234/foo')
req.accept = "application/json"
res = req.get_response(fakes.wsgi_app_v21())
self.assertEqual(404, res.status_int)

View File

@@ -63,6 +63,11 @@ class ConfFixture(config_fixture.Config):
group='api_database')
self.conf.set_default('fatal_exception_format_errors', True)
self.conf.set_default('enabled', True, 'osapi_v21')
# TODO(sdague): this makes our project_id match 'fake' and
# 'openstack' as well. We should fix the tests to use real
# UUIDs then drop this work around.
self.conf.set_default('project_id_regex',
'[0-9a-fopnstk\-]+', 'osapi_v21')
self.conf.set_default('force_dhcp_release', False)
self.conf.set_default('periodic_enable', False)
policy_opts.set_defaults(self.conf)

View File

@@ -0,0 +1,16 @@
---
features:
- Provides API 2.17, which makes the use of project_ids in API urls
optional.
upgrade:
- In order to make project_id optional in urls, we must constrain
the set of allowed values for project_id in our urls. This
defaults to a regex of ``[0-9a-f\-]+``, which will match hex uuids
(with / without dashes), and integers. This covers all known
project_id formats in the wild.
If your site uses other values for project_id, you can set a site
specific validation with ``project_id_regex`` config variable.