Allow normal response to unactioned calls if aggregation disabled
Also some mild refactoring in proxy.py. Now unversioned/unactioned calls can go through without authentication. Renamed self.detailed to the more descriptive strip_details. Co-Authored-By: Kristi Nikolla <knikolla@bu.edu> Change-Id: I5c3a0ab7c020fba480319f11cd1637d5c1f07e11
This commit is contained in:
committed by
Kristi Nikolla
parent
aba889e244
commit
e1fab7c7ce
@@ -41,6 +41,13 @@ def is_valid_uuid(value):
|
||||
return False
|
||||
|
||||
|
||||
def _safe_get(a, index, default=None):
|
||||
try:
|
||||
return a[index]
|
||||
except IndexError:
|
||||
return default
|
||||
|
||||
|
||||
class RequestHandler(object):
|
||||
def __init__(self, method, path, headers):
|
||||
self.method = method
|
||||
@@ -48,12 +55,16 @@ class RequestHandler(object):
|
||||
self.headers = headers
|
||||
|
||||
self.request_path = path.split('/')
|
||||
self.local_token = headers.get('X-AUTH-TOKEN', None)
|
||||
|
||||
self.strip_details = False
|
||||
|
||||
# workaround to fix glance requests
|
||||
# that does not contain image directory
|
||||
if self.request_path[0] in ['v1', 'v2']:
|
||||
self.request_path.insert(0, 'image')
|
||||
|
||||
self.version = _safe_get(self.request_path, 1)
|
||||
self.service_type = self.request_path[0]
|
||||
self.enabled_sps = filter(
|
||||
lambda sp: (self.service_type in
|
||||
@@ -62,38 +73,34 @@ class RequestHandler(object):
|
||||
)
|
||||
|
||||
if len(self.request_path) == 1:
|
||||
# unversioned calls with no action
|
||||
self._forward = self._list_api_versions
|
||||
if CONF.aggregation:
|
||||
# unversioned calls with no action
|
||||
self._forward = self._list_api_versions
|
||||
else:
|
||||
self._forward = self._local_forward
|
||||
self.action = []
|
||||
return
|
||||
elif len(self.request_path) == 2:
|
||||
# versioned calls with no action
|
||||
abort(400)
|
||||
|
||||
self.version = self.request_path[1]
|
||||
|
||||
self.detailed = True
|
||||
if self.service_type == 'image':
|
||||
# /image/{version}/{action}
|
||||
self.action = self.request_path[2:]
|
||||
elif self.service_type == 'volume':
|
||||
# /volume/{version}/{project_id}/{action}
|
||||
self.action = self.request_path[3:]
|
||||
|
||||
# if request is to /volumes, change it
|
||||
# to /volumes/detail for aggregation
|
||||
if self.method == 'GET' \
|
||||
and self.action[-1] == 'volumes':
|
||||
self.detailed = False
|
||||
self.action.insert(len(self.action), 'detail')
|
||||
else:
|
||||
raise ValueError
|
||||
if self.service_type == 'image':
|
||||
# /image/{version}/{action}
|
||||
self.action = self.request_path[2:]
|
||||
elif self.service_type == 'volume':
|
||||
# /volume/{version}/{project_id}/{action}
|
||||
self.action = self.request_path[3:]
|
||||
|
||||
if self.method in ['GET']:
|
||||
self.stream = True
|
||||
else:
|
||||
self.stream = False
|
||||
# if request is to /volumes, change it
|
||||
# to /volumes/detail for aggregation
|
||||
# and set strip_details to true
|
||||
if self.method == 'GET' \
|
||||
and self.action[-1] == 'volumes':
|
||||
self.strip_details = True
|
||||
self.action.insert(len(self.action), 'detail')
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
resource_id = None
|
||||
mapping = None
|
||||
aggregate = False
|
||||
|
||||
@@ -131,21 +138,26 @@ class RequestHandler(object):
|
||||
self._forward = self._search_forward
|
||||
|
||||
def _do_request_on(self, sp, project_id=None):
|
||||
if sp == 'default':
|
||||
auth_session = auth.get_local_auth(self.local_token)
|
||||
else:
|
||||
auth_session = auth.get_sp_auth(sp,
|
||||
self.local_token,
|
||||
project_id)
|
||||
headers = self._prepare_headers(self.headers)
|
||||
headers['X-AUTH-TOKEN'] = auth_session.get_token()
|
||||
|
||||
if self.local_token:
|
||||
if sp == 'default':
|
||||
auth_session = auth.get_local_auth(self.local_token)
|
||||
else:
|
||||
auth_session = auth.get_sp_auth(sp,
|
||||
self.local_token,
|
||||
project_id)
|
||||
headers['X-AUTH-TOKEN'] = auth_session.get_token()
|
||||
project_id = auth_session.get_project_id()
|
||||
else:
|
||||
project_id = None
|
||||
|
||||
url = services.construct_url(
|
||||
sp,
|
||||
self.service_type,
|
||||
self.version,
|
||||
self.action,
|
||||
project_id=auth_session.get_project_id()
|
||||
project_id=project_id
|
||||
)
|
||||
|
||||
LOG.info('%s: %s' % (self.method, url))
|
||||
@@ -225,7 +237,7 @@ class RequestHandler(object):
|
||||
self.service_type,
|
||||
params=request.args.to_dict(),
|
||||
path=request.base_url,
|
||||
detailed=self.detailed),
|
||||
strip_details=self.strip_details),
|
||||
200,
|
||||
content_type='application/json'
|
||||
)
|
||||
@@ -263,6 +275,10 @@ class RequestHandler(object):
|
||||
def chunked(self):
|
||||
return self.headers.get('Transfer-Encoding', '').lower() == 'chunked'
|
||||
|
||||
@property
|
||||
def stream(self):
|
||||
return True if self.method in ['GET'] else False
|
||||
|
||||
|
||||
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT',
|
||||
'DELETE', 'HEAD', 'PATCH'])
|
||||
|
||||
@@ -23,31 +23,29 @@ CONF = config.CONF
|
||||
|
||||
|
||||
def construct_url(service_provider, service_type,
|
||||
version, action, project_id=None):
|
||||
version=None, action=None, project_id=None):
|
||||
"""Construct the full URL for an Openstack API call."""
|
||||
conf = config.get_conf_for_sp(service_provider)
|
||||
|
||||
if service_type == 'image':
|
||||
endpoint = conf.image_endpoint
|
||||
|
||||
return "%(endpoint)s/%(version)s/%(action)s" % {
|
||||
'endpoint': endpoint,
|
||||
'version': version,
|
||||
'action': os.path.join(*action)
|
||||
}
|
||||
url = conf.image_endpoint
|
||||
if version:
|
||||
url = '%s/%s' % (url, version)
|
||||
elif service_type == 'volume':
|
||||
endpoint = conf.volume_endpoint
|
||||
url = conf.volume_endpoint
|
||||
if version:
|
||||
url = '%s/%s' % (url, version)
|
||||
if project_id:
|
||||
url = '%s/%s' % (url, project_id)
|
||||
|
||||
return "%(endpoint)s/%(version)s/%(project)s/%(action)s" % {
|
||||
'endpoint': endpoint,
|
||||
'version': version,
|
||||
'project': project_id,
|
||||
'action': os.path.join(*action)
|
||||
}
|
||||
if action:
|
||||
url = '%s/%s' % (url, os.path.join(*action))
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def aggregate(responses, key, service_type,
|
||||
params=None, path=None, detailed=True):
|
||||
params=None, path=None, strip_details=True):
|
||||
"""Combine responses from several clusters into one response."""
|
||||
if params:
|
||||
limit = int(params.get('limit', 0))
|
||||
@@ -93,7 +91,7 @@ def aggregate(responses, key, service_type,
|
||||
# we automatically make the call to /volumes/detail
|
||||
# because we need sorting information. Here we
|
||||
# remove the extra values /volumes/detail provides
|
||||
if key == 'volumes' and not detailed:
|
||||
if key == 'volumes' and strip_details:
|
||||
resource_list[start:end] = _remove_details(resource_list[start:end])
|
||||
|
||||
response = {key: resource_list[start:end]}
|
||||
|
||||
@@ -313,9 +313,9 @@ class TestServices(testcase.TestCase):
|
||||
Url(VOLUME_VERSIONED))
|
||||
|
||||
def test_remove_details(self):
|
||||
"""Test aggregation on volumes with detailed = False"""
|
||||
"""Test aggregation on volumes with strip_details = True"""
|
||||
response = json.loads(services.aggregate(
|
||||
VOLUMES, 'volumes', 'volume', detailed=False
|
||||
VOLUMES, 'volumes', 'volume', strip_details=True
|
||||
))
|
||||
for v in response['volumes']:
|
||||
self.assertEqual(
|
||||
|
||||
@@ -30,15 +30,18 @@ class TestVolumesV2(base.BaseTest):
|
||||
# migrated to these fixtures.
|
||||
self.load_auth_fixtures()
|
||||
|
||||
def _construct_url(self, auth, volume_id, sp=None):
|
||||
def _construct_url(self, auth=None, target=None, sp=None):
|
||||
if not sp:
|
||||
prefix = '/volume'
|
||||
url = '/volume'
|
||||
else:
|
||||
prefix = self.service_providers[sp]['volume_endpoint']
|
||||
url = self.service_providers[sp]['volume_endpoint']
|
||||
|
||||
return (
|
||||
'%s/v2/%s/volumes/%s' % (prefix, auth.get_project_id(), volume_id)
|
||||
)
|
||||
if auth:
|
||||
url = '%s/v2/%s/volumes' % (url, auth.get_project_id())
|
||||
if target:
|
||||
url = '%s/%s' % (url, target)
|
||||
|
||||
return url
|
||||
|
||||
def test_get_volume_local_mapping(self):
|
||||
volume_id = uuid.uuid4().hex
|
||||
@@ -227,7 +230,7 @@ class TestVolumesV2(base.BaseTest):
|
||||
headers=self.auth.get_headers())
|
||||
self.assertEqual(json.loads(response.data.decode("ascii")), local)
|
||||
|
||||
def test_volume_unversioned_calls_no_action(self):
|
||||
def test_volume_unversioned_calls_no_action_aggregation(self):
|
||||
response = self.app.get(
|
||||
'/volume',
|
||||
headers=self.auth.get_headers())
|
||||
@@ -235,6 +238,18 @@ class TestVolumesV2(base.BaseTest):
|
||||
actual = json.loads(response.data.decode("ascii"))
|
||||
self.assertEqual(len(actual['versions']), 3)
|
||||
|
||||
def test_unversioned_call_no_action_no_aggregation(self):
|
||||
self.config_fixture.load_raw_values(aggregation=False)
|
||||
fake_response = uuid.uuid4().hex
|
||||
|
||||
self.requests_fixture.get(self._construct_url(sp='default'),
|
||||
text=six.u(fake_response),
|
||||
headers={'CONTENT-TYPE': 'application/json'})
|
||||
|
||||
response = self.app.get('/volume')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data, six.b(fake_response))
|
||||
|
||||
def test_volume_versioned_calls_no_action(self):
|
||||
response = self.app.get(
|
||||
'/volume/v2',
|
||||
|
||||
Reference in New Issue
Block a user