166 lines
6.1 KiB
Python
166 lines
6.1 KiB
Python
# Copyright (c) 2016 NTT DATA
|
|
#
|
|
# 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.
|
|
|
|
import re
|
|
|
|
from oslo_log import log as logging
|
|
import six.moves.urllib.parse as urlparse
|
|
|
|
import masakari.conf
|
|
from masakari.i18n import _
|
|
|
|
CONF = masakari.conf.CONF
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def remove_trailing_version_from_href(href):
|
|
"""Removes the api version from the href.
|
|
|
|
Given: 'http://www.masakari.com/ha/v1.1'
|
|
Returns: 'http://www.masakari.com/ha'
|
|
|
|
Given: 'http://www.masakari.com/v1.1'
|
|
Returns: 'http://www.masakari.com'
|
|
|
|
"""
|
|
parsed_url = urlparse.urlsplit(href)
|
|
url_parts = parsed_url.path.rsplit('/', 1)
|
|
|
|
# NOTE: this should match vX.X or vX
|
|
expression = re.compile(r'^v([0-9]+|[0-9]+\.[0-9]+)(/.*|$)')
|
|
if not expression.match(url_parts.pop()):
|
|
LOG.debug('href %s does not contain version', href)
|
|
raise ValueError(_('href %s does not contain version') % href)
|
|
|
|
new_path = url_join(*url_parts)
|
|
parsed_url = list(parsed_url)
|
|
parsed_url[2] = new_path
|
|
return urlparse.urlunsplit(parsed_url)
|
|
|
|
|
|
def url_join(*parts):
|
|
"""Convenience method for joining parts of a URL
|
|
|
|
Any leading and trailing '/' characters are removed, and the parts joined
|
|
together with '/' as a separator. If last element of 'parts' is an empty
|
|
string, the returned URL will have a trailing slash.
|
|
"""
|
|
parts = parts or [""]
|
|
clean_parts = [part.strip("/") for part in parts if part]
|
|
if not parts[-1]:
|
|
# Empty last element should add a trailing slash
|
|
clean_parts.append("")
|
|
return "/".join(clean_parts)
|
|
|
|
|
|
class ViewBuilder(object):
|
|
"""Model API responses as dictionaries."""
|
|
|
|
def _get_project_id(self, request):
|
|
"""Get project id from request url if present or empty string
|
|
otherwise
|
|
"""
|
|
project_id = request.environ["masakari.context"].project_id
|
|
if project_id in request.url:
|
|
return project_id
|
|
return ''
|
|
|
|
def _get_links(self, request, identifier, collection_name):
|
|
return [
|
|
{
|
|
"rel": "self",
|
|
"href": self._get_href_link(request, identifier,
|
|
collection_name),
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self._get_bookmark_link(request,
|
|
identifier,
|
|
collection_name),
|
|
}
|
|
]
|
|
|
|
def _get_next_link(self, request, identifier, collection_name):
|
|
"""Return href string with proper limit and marker params."""
|
|
params = request.params.copy()
|
|
params["marker"] = identifier
|
|
prefix = self._update_masakari_link_prefix(request.application_url)
|
|
url = url_join(prefix,
|
|
self._get_project_id(request),
|
|
collection_name)
|
|
return "%s?%s" % (url, urlparse.urlencode(params))
|
|
|
|
def _get_href_link(self, request, identifier, collection_name):
|
|
"""Return an href string pointing to this object."""
|
|
prefix = self._update_masakari_link_prefix(request.application_url)
|
|
return url_join(prefix,
|
|
self._get_project_id(request),
|
|
collection_name,
|
|
str(identifier))
|
|
|
|
def _get_bookmark_link(self, request, identifier, collection_name):
|
|
"""Create a URL that refers to a specific resource."""
|
|
base_url = remove_trailing_version_from_href(request.application_url)
|
|
base_url = self._update_masakari_link_prefix(base_url)
|
|
return url_join(base_url,
|
|
self._get_project_id(request),
|
|
collection_name,
|
|
str(identifier))
|
|
|
|
def _get_collection_links(self,
|
|
request,
|
|
items,
|
|
collection_name,
|
|
id_key="uuid"):
|
|
"""Retrieve 'next' link, if applicable. This is included if:
|
|
1) 'limit' param is specified and equals the number of items.
|
|
2) 'limit' param is specified but it exceeds CONF.osapi_max_limit,
|
|
in this case the number of items is CONF.osapi_max_limit.
|
|
3) 'limit' param is NOT specified but the number of items is
|
|
CONF.osapi_max_limit.
|
|
"""
|
|
links = []
|
|
max_items = min(
|
|
int(request.params.get("limit", CONF.osapi_max_limit)),
|
|
CONF.osapi_max_limit)
|
|
if max_items and max_items == len(items):
|
|
last_item = items[-1]
|
|
if id_key in last_item:
|
|
last_item_id = last_item[id_key]
|
|
elif 'id' in last_item:
|
|
last_item_id = last_item["id"]
|
|
else:
|
|
last_item_id = last_item["flavorid"]
|
|
links.append({
|
|
"rel": "next",
|
|
"href": self._get_next_link(request,
|
|
last_item_id,
|
|
collection_name),
|
|
})
|
|
return links
|
|
|
|
def _update_link_prefix(self, orig_url, prefix):
|
|
if not prefix:
|
|
return orig_url
|
|
url_parts = list(urlparse.urlsplit(orig_url))
|
|
prefix_parts = list(urlparse.urlsplit(prefix))
|
|
url_parts[0:2] = prefix_parts[0:2]
|
|
url_parts[2] = prefix_parts[2] + url_parts[2]
|
|
return urlparse.urlunsplit(url_parts).rstrip('/')
|
|
|
|
def _update_masakari_link_prefix(self, orig_url):
|
|
return self._update_link_prefix(orig_url,
|
|
CONF.osapi_masakari_link_prefix)
|