
https://wiki.openstack.org/wiki/Python3 Change-Id: I09ceb7c4819573f404359af5200e990b1e6070e2
408 lines
15 KiB
Python
408 lines
15 KiB
Python
# Copyright 2014
|
|
# The Cloudscaling Group, Inc.
|
|
#
|
|
# 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.
|
|
|
|
"""Base GCE API controller"""
|
|
|
|
import os.path
|
|
import re
|
|
from webob import exc
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import timeutils
|
|
import six
|
|
|
|
from gceapi.api import clients
|
|
from gceapi.api import operation_api
|
|
from gceapi.api import operation_util
|
|
from gceapi.api import scopes
|
|
from gceapi.api import utils
|
|
from gceapi import exception
|
|
from gceapi.i18n import _
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
FLAGS = cfg.CONF
|
|
|
|
|
|
class Controller(object):
|
|
"""Base controller
|
|
|
|
Implements base CRUD methods.
|
|
Individual GCE controllers should inherit this and:
|
|
- implement format_item() method,
|
|
- override _get_type() method,
|
|
- add necessary specific request handlers,
|
|
- use _api to hold instance of related GCE API (see base_api.py).
|
|
"""
|
|
|
|
_api = None
|
|
|
|
# Initialization
|
|
def __init__(self, api):
|
|
"""Base initialization.
|
|
|
|
Inherited classes should init _api and call super().
|
|
"""
|
|
|
|
self._api = api
|
|
self._type_name = self._api._get_type()
|
|
self._collection_name = utils.get_collection_name(self._type_name)
|
|
self._type_kind = utils.get_type_kind(self._type_name)
|
|
self._list_kind = utils.get_list_kind(self._type_name)
|
|
self._aggregated_kind = utils.get_aggregated_kind(self._type_name)
|
|
self._operation_api = operation_api.API()
|
|
|
|
def process_result(self, request, action, action_result):
|
|
context = self._get_context(request)
|
|
operation = operation_util.save_operation(context, action_result)
|
|
if operation is not None:
|
|
scope = self._operation_api.get_scopes(context, operation)[0]
|
|
action_result = self._format_operation(request, operation, scope)
|
|
|
|
if isinstance(action_result, Exception):
|
|
return self._format_error(action_result)
|
|
if action_result is None:
|
|
return None, 204
|
|
return self._format_output(request, action, action_result), 200
|
|
|
|
# Base methods, should be overridden
|
|
|
|
def format_item(self, request, image, scope):
|
|
"""Main item resource conversion routine
|
|
|
|
Overriden in inherited classes should implement conversion of
|
|
OpenStack resource into GCE resource.
|
|
"""
|
|
|
|
raise exc.HTTPNotImplemented
|
|
|
|
# Actions
|
|
def index(self, req, scope_id=None):
|
|
"""GCE list requests, global or with zone/region specified."""
|
|
|
|
context = self._get_context(req)
|
|
scope = self._get_scope(req, scope_id)
|
|
|
|
items = self._api.get_items(context, scope)
|
|
items = [{
|
|
"scope": scope,
|
|
"item": self.format_item(req, i, scope)
|
|
} for i in items]
|
|
items = self._filter_items(req, items)
|
|
items, next_page_token = self._page_items(req, items)
|
|
items = [i["item"] for i in items]
|
|
|
|
return self._format_list(req, items, next_page_token, scope)
|
|
|
|
def show(self, req, id=None, scope_id=None):
|
|
"""GCE get requests, global or zone/region specified."""
|
|
|
|
context = self._get_context(req)
|
|
scope = self._get_scope(req, scope_id)
|
|
try:
|
|
item = self._api.get_item(context, id, scope)
|
|
return self.format_item(req, item, scope)
|
|
except (exception.NotFound, KeyError, IndexError) as ex:
|
|
LOG.exception(ex)
|
|
msg = _("Resource '%s' could not be found") % id
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
def aggregated_list(self, req):
|
|
"""GCE aggregated list requests for all zones/regions."""
|
|
|
|
context = self._get_context(req)
|
|
items = list()
|
|
for item in self._api.get_items(context, None):
|
|
for scope in self._api.get_scopes(context, item):
|
|
items.append({
|
|
"scope": scope,
|
|
"item": self.format_item(req, item, scope)
|
|
})
|
|
items = self._filter_items(req, items)
|
|
items, next_page_token = self._page_items(req, items)
|
|
|
|
items_by_scopes = {}
|
|
for item in items:
|
|
scope_path = item["scope"].get_path()
|
|
items_by_scope = items_by_scopes.setdefault(scope_path,
|
|
{self._collection_name: []})[self._collection_name]
|
|
items_by_scope.append(item["item"])
|
|
|
|
return self._format_list(req, items_by_scopes, next_page_token,
|
|
scopes.AggregatedScope())
|
|
|
|
def delete(self, req, id, scope_id=None):
|
|
"""GCE delete requests."""
|
|
|
|
scope = self._get_scope(req, scope_id)
|
|
context = self._get_context(req)
|
|
operation_util.init_operation(context, "delete",
|
|
self._type_name, id, scope)
|
|
try:
|
|
self._api.delete_item(context, id, scope)
|
|
except (exception.NotFound, KeyError, IndexError) as ex:
|
|
LOG.exception(ex)
|
|
msg = _("Resource '%s' could not be found") % id
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
def create(self, req, body, scope_id=None):
|
|
"""GCE add requests."""
|
|
|
|
scope = self._get_scope(req, scope_id)
|
|
context = self._get_context(req)
|
|
operation_util.init_operation(context, "insert",
|
|
self._type_name, body["name"], scope)
|
|
self._api.add_item(context, body['name'], body, scope)
|
|
|
|
# Filtering
|
|
def _filter_items(self, req, items):
|
|
"""Filtering result list
|
|
|
|
Only one filter is supported(eg. by one field)
|
|
Only two comparison strings are supported: 'eq' and 'ne'
|
|
There are no logical expressions with fields
|
|
"""
|
|
if not items:
|
|
return items
|
|
if "filter" not in req.params:
|
|
return items
|
|
|
|
filter_def = req.params["filter"].split()
|
|
if len(filter_def) != 3:
|
|
# TODO(apavlov): raise exception
|
|
return items
|
|
if filter_def[1] != "eq" and filter_def[1] != "ne":
|
|
# TODO(apavlov): raise exception
|
|
return items
|
|
if filter_def[0] not in items[0]["item"]:
|
|
# TODO(apavlov): raise exception
|
|
return items
|
|
|
|
filter_field = filter_def[0]
|
|
filter_cmp = filter_def[1] == "eq"
|
|
filter_pattern = filter_def[2]
|
|
if filter_pattern[0] == "'" and filter_pattern[-1] == "'":
|
|
filter_pattern = filter_pattern[1:-1]
|
|
|
|
result_list = list()
|
|
for item in items:
|
|
field = item["item"][filter_field]
|
|
result = re.match(filter_pattern, field)
|
|
if filter_cmp != (result is None):
|
|
result_list.append(item)
|
|
|
|
return result_list
|
|
|
|
# Paging
|
|
def _page_items(self, req, items):
|
|
if not items:
|
|
return items, None
|
|
if "maxResults" not in req.params:
|
|
return items, None
|
|
|
|
limit = int(req.params["maxResults"])
|
|
if limit >= len(items):
|
|
return items, None
|
|
|
|
page_index = int(req.params.get("pageToken", 0))
|
|
if page_index < 0 or page_index * limit > len(items):
|
|
# TODO(apavlov): raise exception
|
|
return [], None
|
|
|
|
items.sort(None, lambda x: x["item"].get("name"))
|
|
start = limit * page_index
|
|
if start + limit >= len(items):
|
|
return items[start:], None
|
|
|
|
return items[start:start + limit], str(page_index + 1)
|
|
|
|
# Utility
|
|
def _get_context(self, req):
|
|
return req.environ['gceapi.context']
|
|
|
|
def _get_scope(self, req, scope_id):
|
|
scope = scopes.construct_from_path(req.path_info, scope_id)
|
|
if scope is None:
|
|
return None
|
|
scope_api = scope.get_scope_api()
|
|
if scope_api is not None:
|
|
try:
|
|
context = self._get_context(req)
|
|
scope_api.get_item(context, scope.get_name(), None)
|
|
except ValueError as ex:
|
|
raise exc.HTTPNotFound(detail=ex)
|
|
|
|
return scope
|
|
|
|
# Result formatting
|
|
def _format_date(self, date_string):
|
|
"""Returns standard format for given date."""
|
|
if date_string is None:
|
|
return None
|
|
if isinstance(date_string, six.string_types):
|
|
date_string = timeutils.parse_isotime(date_string)
|
|
return date_string.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
|
|
def _get_id(self, link):
|
|
hashed_link = hash(link)
|
|
if hashed_link < 0:
|
|
hashed_link = -hashed_link
|
|
return str(hashed_link)
|
|
|
|
def _qualify(self, request, controller, identifier, scope):
|
|
"""Creates fully qualified selfLink for an item or collection
|
|
|
|
Specific formatting for projects and zones/regions,
|
|
'global' prefix For global resources,
|
|
'zones/zone_id' prefix for zone(similar for regions) resources.
|
|
"""
|
|
|
|
context = self._get_context(request)
|
|
public_url = clients.url_for(context, "gceapi")
|
|
if public_url:
|
|
public_url = public_url.rstrip("/") + "/"\
|
|
+ request.script_name.lstrip("/")
|
|
else:
|
|
public_url = request.application_url
|
|
|
|
result = os.path.join(
|
|
public_url, context.project_name)
|
|
if controller:
|
|
if scope:
|
|
result = os.path.join(result, scope.get_path())
|
|
result = os.path.join(result, controller)
|
|
if identifier:
|
|
result = os.path.join(result, identifier)
|
|
return result
|
|
|
|
def _format_item(self, request, result_dict, scope):
|
|
return self._add_item_header(request, result_dict, scope,
|
|
self._type_kind, self._collection_name)
|
|
|
|
def _format_operation(self, request, operation, scope):
|
|
result_dict = {
|
|
"name": operation["name"],
|
|
"operationType": operation["type"],
|
|
"insertTime": operation["insert_time"],
|
|
"startTime": operation["start_time"],
|
|
"status": operation["status"],
|
|
"progress": operation["progress"],
|
|
"user": operation["user"],
|
|
}
|
|
result_dict["targetLink"] = self._qualify(
|
|
request, utils.get_collection_name(operation["target_type"]),
|
|
operation["target_name"], scope)
|
|
result_dict["targetId"] = self._get_id(result_dict["targetLink"])
|
|
if "end_time" in operation:
|
|
result_dict["endTime"] = operation["end_time"]
|
|
if "error_code" in operation:
|
|
result_dict.update({
|
|
"httpErrorStatusCode": operation["error_code"],
|
|
"httpErrorMessage": operation["error_message"],
|
|
"error": {"errors": operation["errors"]},
|
|
})
|
|
type_name = self._operation_api._get_type()
|
|
return self._add_item_header(request, result_dict, scope,
|
|
utils.get_type_kind(type_name),
|
|
utils.get_collection_name(type_name))
|
|
|
|
def _add_item_header(self, request, result_dict, scope,
|
|
_type_kind, _collection_name):
|
|
if scope is not None and scope.get_name() is not None:
|
|
result_dict[scope.get_type()] = self._qualify(
|
|
request, scope.get_collection(), scope.get_name(), None)
|
|
result_dict["kind"] = _type_kind
|
|
result_dict["selfLink"] = self._qualify(
|
|
request, _collection_name, result_dict.get("name"), scope)
|
|
result_dict["id"] = self._get_id(result_dict["selfLink"])
|
|
return result_dict
|
|
|
|
def _format_list(self, request, result_list, next_page_token, scope):
|
|
result_dict = {}
|
|
result_dict["items"] = result_list
|
|
if next_page_token:
|
|
result_dict["nextPageToken"] = next_page_token
|
|
result_dict["kind"] = (self._aggregated_kind
|
|
if scope and isinstance(scope, scopes.AggregatedScope)
|
|
else self._list_kind)
|
|
|
|
context = self._get_context(request)
|
|
list_id = os.path.join("projects", context.project_name)
|
|
if scope:
|
|
list_id = os.path.join(list_id, scope.get_path())
|
|
list_id = os.path.join(list_id, self._collection_name)
|
|
result_dict["id"] = list_id
|
|
|
|
result_dict["selfLink"] = self._qualify(
|
|
request, self._collection_name, None, scope)
|
|
return result_dict
|
|
|
|
def _format_error(self, ex_value):
|
|
if isinstance(ex_value, exception.NotAuthorized):
|
|
msg = _('Unauthorized')
|
|
code = 401
|
|
elif isinstance(ex_value, exc.HTTPException):
|
|
msg = ex_value.explanation
|
|
code = ex_value.code
|
|
elif isinstance(ex_value, exception.GceapiException):
|
|
msg = ex_value.args[0]
|
|
code = ex_value.code
|
|
else:
|
|
msg = _('Internal server error')
|
|
code = 500
|
|
|
|
return {
|
|
'error': {'errors': [{'message': msg}]},
|
|
'code': code,
|
|
'message': msg
|
|
}, code
|
|
|
|
def _format_output(self, request, action, action_result):
|
|
# TODO(ft): this metod must be safe and ignore unknown fields
|
|
fields = request.params.get('fields', None)
|
|
# TODO(ft): GCE can also format results of other action
|
|
if action not in ('index', 'show') or fields is None:
|
|
return action_result
|
|
|
|
if action == 'show':
|
|
action_result = utils.apply_template(fields, action_result)
|
|
return action_result
|
|
sp = utils.split_by_comma(fields)
|
|
top_level = []
|
|
items = []
|
|
for string in sp:
|
|
if 'items' in string:
|
|
items.append(string)
|
|
else:
|
|
top_level.append(string)
|
|
res = {}
|
|
if len(items) > 0:
|
|
res['items'] = []
|
|
for string in top_level:
|
|
dct = utils.apply_template(string, action_result)
|
|
for key, val in dct.items():
|
|
res[key] = val
|
|
for string in items:
|
|
if '(' in string:
|
|
dct = utils.apply_template(string, action_result)
|
|
for key, val in dct.items():
|
|
res[key] = val
|
|
elif string.startswith('items/'):
|
|
string = string[len('items/'):]
|
|
for element in action_result['items']:
|
|
dct = utils.apply_template(string, element)
|
|
res['items'].append(dct)
|
|
|
|
return res
|