Merge "Make project_id optional in v2.1 urls"
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.17",
|
||||
"version": "2.18",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.17",
|
||||
"version": "2.18",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']})
|
||||
|
||||
@@ -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']})
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.17",
|
||||
"version": "2.18",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.17",
|
||||
"version": "2.18",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
16
releasenotes/notes/optional_project_id-6aebf1cb394d498f.yaml
Normal file
16
releasenotes/notes/optional_project_id-6aebf1cb394d498f.yaml
Normal 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.
|
||||
Reference in New Issue
Block a user