Merge "Adapt error response body in nova and cinder api"
This commit is contained in:
commit
0686006b49
|
@ -15,11 +15,9 @@
|
|||
|
||||
import urlparse
|
||||
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
from pecan import response
|
||||
from pecan import Response
|
||||
from pecan import rest
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
@ -31,6 +29,7 @@ import tricircle.common.context as t_context
|
|||
from tricircle.common import httpclient as hclient
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common.i18n import _LE
|
||||
from tricircle.common import utils
|
||||
|
||||
import tricircle.db.api as db_api
|
||||
from tricircle.db import core
|
||||
|
@ -49,8 +48,8 @@ class VolumeController(rest.RestController):
|
|||
context = t_context.extract_context_from_environ()
|
||||
|
||||
if 'volume' not in kw:
|
||||
pecan.abort(400, _('Volume not found in request body'))
|
||||
return
|
||||
return utils.format_cinder_error(
|
||||
400, _("Missing required element 'volume' in request body."))
|
||||
|
||||
az = kw['volume'].get('availability_zone', '')
|
||||
pod, pod_az = az_ag.get_pod_by_az_tenant(
|
||||
|
@ -58,15 +57,14 @@ class VolumeController(rest.RestController):
|
|||
az_name=az,
|
||||
tenant_id=self.tenant_id)
|
||||
if not pod:
|
||||
pecan.abort(500, _('Pod not configured or scheduling failure'))
|
||||
LOG.error(_LE("Pod not configured or scheduling failure"))
|
||||
return
|
||||
return utils.format_cinder_error(
|
||||
500, _('Pod not configured or scheduling failure'))
|
||||
|
||||
t_pod = db_api.get_top_pod(context)
|
||||
if not t_pod:
|
||||
pecan.abort(500, _('Top Pod not configured'))
|
||||
LOG.error(_LE("Top Pod not configured"))
|
||||
return
|
||||
return utils.format_cinder_error(500, _('Top Pod not configured'))
|
||||
|
||||
# TODO(joehuang): get release from pod configuration,
|
||||
# to convert the content
|
||||
|
@ -82,10 +80,10 @@ class VolumeController(rest.RestController):
|
|||
s_type=cons.ST_CINDER)
|
||||
|
||||
if s_ctx['b_url'] == '':
|
||||
pecan.abort(500, _('bottom pod endpoint incorrect'))
|
||||
LOG.error(_LE("bottom pod endpoint incorrect %s") %
|
||||
LOG.error(_LE("Bottom Pod endpoint incorrect %s") %
|
||||
pod['pod_name'])
|
||||
return
|
||||
return utils.format_cinder_error(
|
||||
500, _('Bottom Pod endpoint incorrect'))
|
||||
|
||||
b_headers = self._convert_header(t_release,
|
||||
b_release,
|
||||
|
@ -141,8 +139,8 @@ class VolumeController(rest.RestController):
|
|||
'bottom_id': b_vol_ret['id'],
|
||||
'pod_id': pod['pod_id'],
|
||||
'exception': e})
|
||||
return Response(_('Failed to create volume '
|
||||
'resource routing'), 500)
|
||||
return utils.format_cinder_error(
|
||||
500, _('Failed to create volume resource routing'))
|
||||
|
||||
ret_vol = self._convert_object(b_release, t_release,
|
||||
b_vol_ret,
|
||||
|
@ -152,7 +150,7 @@ class VolumeController(rest.RestController):
|
|||
|
||||
return {'volume': ret_vol}
|
||||
|
||||
return {'error': b_ret_body}
|
||||
return b_ret_body
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
def get_one(self, _id):
|
||||
|
@ -171,10 +169,12 @@ class VolumeController(rest.RestController):
|
|||
|
||||
s_ctx = self._get_res_routing_ref(context, _id, request.url)
|
||||
if not s_ctx:
|
||||
return Response(_('Failed to find resource'), 404)
|
||||
return utils.format_cinder_error(
|
||||
404, _('Volume %s could not be found.') % _id)
|
||||
|
||||
if s_ctx['b_url'] == '':
|
||||
return Response(_('bottom pod endpoint incorrect'), 404)
|
||||
return utils.format_cinder_error(
|
||||
404, _('Bottom Pod endpoint incorrect'))
|
||||
|
||||
resp = hclient.forward_req(context, 'GET',
|
||||
b_headers,
|
||||
|
@ -290,10 +290,12 @@ class VolumeController(rest.RestController):
|
|||
|
||||
s_ctx = self._get_res_routing_ref(context, _id, request.url)
|
||||
if not s_ctx:
|
||||
return Response(_('Resource not found'), 404)
|
||||
return utils.format_cinder_error(
|
||||
404, _('Volume %s could not be found.') % _id)
|
||||
|
||||
if s_ctx['b_url'] == '':
|
||||
return Response(_('Bottom pod endpoint incorrect'), 404)
|
||||
return utils.format_cinder_error(
|
||||
404, _('Bottom Pod endpoint incorrect'))
|
||||
|
||||
b_headers = self._convert_header(t_release,
|
||||
b_release,
|
||||
|
@ -351,10 +353,12 @@ class VolumeController(rest.RestController):
|
|||
|
||||
s_ctx = self._get_res_routing_ref(context, _id, request.url)
|
||||
if not s_ctx:
|
||||
return Response(_('Failed to find resource'), 404)
|
||||
return utils.format_cinder_error(
|
||||
404, _('Volume %s could not be found.') % _id)
|
||||
|
||||
if s_ctx['b_url'] == '':
|
||||
return Response(_('bottom pod endpoint incorrect'), 404)
|
||||
return utils.format_cinder_error(
|
||||
404, _('Bottom Pod endpoint incorrect'))
|
||||
|
||||
b_headers = self._convert_header(t_release,
|
||||
b_release,
|
||||
|
|
|
@ -23,6 +23,7 @@ import tricircle.common.client as t_client
|
|||
from tricircle.common import constants
|
||||
import tricircle.common.context as t_context
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import utils
|
||||
import tricircle.db.api as db_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -52,13 +53,6 @@ class ActionController(rest.RestController):
|
|||
client = self._get_client(pod_name)
|
||||
return client.action_servers(context, 'stop', self.server_id)
|
||||
|
||||
@staticmethod
|
||||
def _format_error(code, message):
|
||||
pecan.response.status = code
|
||||
# format error message in this form so nova client can
|
||||
# correctly parse it
|
||||
return {'Error': {'message': message, 'code': code}}
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
def post(self, **kw):
|
||||
context = t_context.extract_context_from_environ()
|
||||
|
@ -70,12 +64,14 @@ class ActionController(rest.RestController):
|
|||
action_handle = self.handle_map[_type]
|
||||
action_type = _type
|
||||
if not action_handle:
|
||||
return self._format_error(400, _('Server action not supported'))
|
||||
return utils.format_nova_error(
|
||||
400, _('Server action not supported'))
|
||||
|
||||
server_mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
context, self.server_id, constants.RT_SERVER)
|
||||
if not server_mappings:
|
||||
return self._format_error(404, _('Server not found'))
|
||||
return utils.format_nova_error(
|
||||
404, _('Server %s could not be found') % self.server_id)
|
||||
|
||||
pod_name = server_mappings[0][0]['pod_name']
|
||||
try:
|
||||
|
@ -96,4 +92,4 @@ class ActionController(rest.RestController):
|
|||
if ex_message:
|
||||
message = ex_message
|
||||
LOG.error(message)
|
||||
return self._format_error(code, message)
|
||||
return utils.format_nova_error(code, message)
|
||||
|
|
|
@ -22,6 +22,8 @@ import oslo_db.exception as db_exc
|
|||
from tricircle.common import az_ag
|
||||
import tricircle.common.context as t_context
|
||||
import tricircle.common.exceptions as t_exc
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import utils
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
@ -36,20 +38,25 @@ class AggregateActionController(rest.RestController):
|
|||
def post(self, **kw):
|
||||
context = t_context.extract_context_from_environ()
|
||||
if not context.is_admin:
|
||||
pecan.abort(400, 'Admin role required to operate aggregates')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
403, _("Policy doesn't allow os_compute_api:os-aggregates:"
|
||||
"index to be performed."))
|
||||
try:
|
||||
with context.session.begin():
|
||||
core.get_resource(context, models.Aggregate, self.aggregate_id)
|
||||
except t_exc.ResourceNotFound:
|
||||
pecan.abort(400, 'Aggregate not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Aggregate %s could not be found.') % self.aggregate_id)
|
||||
if 'add_host' in kw or 'remove_host' in kw:
|
||||
pecan.abort(400, 'Add and remove host action not supported')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Add and remove host action not supported'))
|
||||
# TODO(zhiyuan) handle aggregate metadata updating
|
||||
aggregate = az_ag.get_one_ag(context, self.aggregate_id)
|
||||
return {'aggregate': aggregate}
|
||||
try:
|
||||
aggregate = az_ag.get_one_ag(context, self.aggregate_id)
|
||||
return {'aggregate': aggregate}
|
||||
except Exception:
|
||||
return utils.format_nova_error(
|
||||
500, _('Aggregate operation on %s failed') % self.aggregate_id)
|
||||
|
||||
|
||||
class AggregateController(rest.RestController):
|
||||
|
@ -67,11 +74,12 @@ class AggregateController(rest.RestController):
|
|||
def post(self, **kw):
|
||||
context = t_context.extract_context_from_environ()
|
||||
if not context.is_admin:
|
||||
pecan.abort(400, 'Admin role required to create aggregates')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
403, _("Policy doesn't allow os_compute_api:os-aggregates:"
|
||||
"index to be performed."))
|
||||
if 'aggregate' not in kw:
|
||||
pecan.abort(400, 'Request body not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('aggregate is not set'))
|
||||
|
||||
host_aggregate = kw['aggregate']
|
||||
name = host_aggregate['name'].strip()
|
||||
|
@ -85,11 +93,11 @@ class AggregateController(rest.RestController):
|
|||
ag_name=name,
|
||||
az_name=avail_zone)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
pecan.abort(409, 'Aggregate already exists')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
409, _('Aggregate %s already exists.') % name)
|
||||
except Exception:
|
||||
pecan.abort(500, 'Fail to create host aggregate')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
500, _('Fail to create aggregate'))
|
||||
|
||||
return {'aggregate': aggregate}
|
||||
|
||||
|
@ -101,8 +109,11 @@ class AggregateController(rest.RestController):
|
|||
aggregate = az_ag.get_one_ag(context, _id)
|
||||
return {'aggregate': aggregate}
|
||||
except t_exc.ResourceNotFound:
|
||||
pecan.abort(404, 'Aggregate not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Aggregate %s could not be found.') % _id)
|
||||
except Exception:
|
||||
return utils.format_nova_error(
|
||||
500, _('Fail to get aggregate %s') % _id)
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
def get_all(self):
|
||||
|
@ -112,8 +123,7 @@ class AggregateController(rest.RestController):
|
|||
with context.session.begin():
|
||||
aggregates = az_ag.get_all_ag(context)
|
||||
except Exception:
|
||||
pecan.abort(500, 'Fail to get all host aggregates')
|
||||
return
|
||||
return utils.format_nova_error(500, _('Fail to list aggregates'))
|
||||
return {'aggregates': aggregates}
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
|
@ -124,5 +134,8 @@ class AggregateController(rest.RestController):
|
|||
az_ag.delete_ag(context, _id)
|
||||
pecan.response.status = 200
|
||||
except t_exc.ResourceNotFound:
|
||||
pecan.abort(404, 'Aggregate not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Aggregate %s could not be found.') % _id)
|
||||
except Exception:
|
||||
return utils.format_nova_error(
|
||||
500, _('Fail to delete aggregate %s') % _id)
|
||||
|
|
|
@ -20,6 +20,7 @@ from pecan import rest
|
|||
import oslo_db.exception as db_exc
|
||||
|
||||
import tricircle.common.context as t_context
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import utils
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
@ -37,15 +38,17 @@ class FlavorManageController(rest.RestController):
|
|||
def post(self, **kw):
|
||||
context = t_context.extract_context_from_environ()
|
||||
if not context.is_admin:
|
||||
pecan.abort(400, 'Admin role required to create flavors')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
403, _("Policy doesn't allow os_compute_api:os-flavor-manage "
|
||||
"to be performed."))
|
||||
|
||||
required_fields = ['name', 'ram', 'vcpus', 'disk']
|
||||
if 'flavor' not in kw:
|
||||
pass
|
||||
utils.format_nova_error(400, _('flavor is not set'))
|
||||
if not utils.validate_required_fields_set(kw['flavor'],
|
||||
required_fields):
|
||||
pass
|
||||
utils.format_nova_error(
|
||||
400, _('Invalid input for field/attribute flavor.'))
|
||||
|
||||
flavor_dict = {
|
||||
'name': kw['flavor']['name'],
|
||||
|
@ -63,28 +66,36 @@ class FlavorManageController(rest.RestController):
|
|||
with context.session.begin():
|
||||
flavor = core.create_resource(
|
||||
context, models.InstanceTypes, flavor_dict)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
pecan.abort(409, 'Flavor already exists')
|
||||
return
|
||||
except db_exc.DBDuplicateEntry as e:
|
||||
if 'flavorid' in e.columns:
|
||||
return utils.format_nova_error(
|
||||
409, _('Flavor with ID %s already '
|
||||
'exists.') % flavor_dict['flavorid'])
|
||||
else:
|
||||
return utils.format_nova_error(
|
||||
409, _('Flavor with name %s already '
|
||||
'exists.') % flavor_dict['name'])
|
||||
except Exception:
|
||||
pecan.abort(500, 'Fail to create flavor')
|
||||
return
|
||||
return utils.format_nova_error(500, _('Failed to create flavor'))
|
||||
|
||||
return {'flavor': flavor}
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
def delete(self, _id):
|
||||
context = t_context.extract_context_from_environ()
|
||||
with context.session.begin():
|
||||
flavors = core.query_resource(context, models.InstanceTypes,
|
||||
[{'key': 'flavorid',
|
||||
'comparator': 'eq',
|
||||
'value': _id}], [])
|
||||
if not flavors:
|
||||
pecan.abort(404, 'Flavor not found')
|
||||
return
|
||||
core.delete_resource(context,
|
||||
models.InstanceTypes, flavors[0]['id'])
|
||||
try:
|
||||
with context.session.begin():
|
||||
flavors = core.query_resource(context, models.InstanceTypes,
|
||||
[{'key': 'flavorid',
|
||||
'comparator': 'eq',
|
||||
'value': _id}], [])
|
||||
if not flavors:
|
||||
return utils.format_nova_error(
|
||||
404, _('Flavor %s could not be found') % _id)
|
||||
core.delete_resource(context, models.InstanceTypes,
|
||||
flavors[0]['id'])
|
||||
except Exception:
|
||||
return utils.format_nova_error(500, _('Failed to delete flavor'))
|
||||
pecan.response.status = 202
|
||||
return
|
||||
|
||||
|
@ -103,17 +114,17 @@ class FlavorController(rest.RestController):
|
|||
def post(self, **kw):
|
||||
context = t_context.extract_context_from_environ()
|
||||
if not context.is_admin:
|
||||
pecan.abort(400, 'Admin role required to create flavors')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
403, _("Policy doesn't allow os_compute_api:os-flavor-manage "
|
||||
"to be performed."))
|
||||
|
||||
required_fields = ['name', 'ram', 'vcpus', 'disk']
|
||||
if 'flavor' not in kw:
|
||||
pecan.abort(400, 'Request body not found')
|
||||
return
|
||||
utils.format_nova_error(400, _('flavor is not set'))
|
||||
if not utils.validate_required_fields_set(kw['flavor'],
|
||||
required_fields):
|
||||
pecan.abort(400, 'Required field not set')
|
||||
return
|
||||
utils.format_nova_error(
|
||||
400, _('Invalid input for field/attribute flavor.'))
|
||||
|
||||
flavor_dict = {
|
||||
'name': kw['flavor']['name'],
|
||||
|
@ -131,12 +142,17 @@ class FlavorController(rest.RestController):
|
|||
with context.session.begin():
|
||||
flavor = core.create_resource(
|
||||
context, models.InstanceTypes, flavor_dict)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
pecan.abort(409, 'Flavor already exists')
|
||||
return
|
||||
except db_exc.DBDuplicateEntry as e:
|
||||
if 'flavorid' in e.columns:
|
||||
return utils.format_nova_error(
|
||||
409, _('Flavor with ID %s already '
|
||||
'exists.') % flavor_dict['flavorid'])
|
||||
else:
|
||||
return utils.format_nova_error(
|
||||
409, _('Flavor with name %s already '
|
||||
'exists.') % flavor_dict['name'])
|
||||
except Exception:
|
||||
pecan.abort(500, 'Fail to create flavor')
|
||||
return
|
||||
utils.format_nova_error(500, _('Failed to create flavor'))
|
||||
|
||||
flavor['id'] = flavor['flavorid']
|
||||
del flavor['flavorid']
|
||||
|
@ -163,8 +179,8 @@ class FlavorController(rest.RestController):
|
|||
'comparator': 'eq',
|
||||
'value': _id}], [])
|
||||
if not flavors:
|
||||
pecan.abort(404, 'Flavor not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Flavor %s could not be found') % _id)
|
||||
flavor = flavors[0]
|
||||
flavor['id'] = flavor['flavorid']
|
||||
del flavor['flavorid']
|
||||
|
@ -184,15 +200,18 @@ class FlavorController(rest.RestController):
|
|||
def delete(self, _id):
|
||||
# TODO(zhiyuan) handle foreign key constraint
|
||||
context = t_context.extract_context_from_environ()
|
||||
with context.session.begin():
|
||||
flavors = core.query_resource(context, models.InstanceTypes,
|
||||
[{'key': 'flavorid',
|
||||
'comparator': 'eq',
|
||||
'value': _id}], [])
|
||||
if not flavors:
|
||||
pecan.abort(404, 'Flavor not found')
|
||||
return
|
||||
core.delete_resource(context,
|
||||
models.InstanceTypes, flavors[0]['id'])
|
||||
try:
|
||||
with context.session.begin():
|
||||
flavors = core.query_resource(context, models.InstanceTypes,
|
||||
[{'key': 'flavorid',
|
||||
'comparator': 'eq',
|
||||
'value': _id}], [])
|
||||
if not flavors:
|
||||
return utils.format_nova_error(
|
||||
404, _('Flavor %s could not be found') % _id)
|
||||
core.delete_resource(context,
|
||||
models.InstanceTypes, flavors[0]['id'])
|
||||
except Exception:
|
||||
return utils.format_nova_error(500, _('Failed to delete flavor'))
|
||||
pecan.response.status = 202
|
||||
return
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import rest
|
||||
import re
|
||||
|
@ -22,6 +21,8 @@ import urlparse
|
|||
import tricircle.common.client as t_client
|
||||
from tricircle.common import constants
|
||||
import tricircle.common.context as t_context
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import utils
|
||||
import tricircle.db.api as db_api
|
||||
|
||||
|
||||
|
@ -141,8 +142,7 @@ class ImageController(rest.RestController):
|
|||
context = t_context.extract_context_from_environ()
|
||||
image = self.client.get_images(context, _id)
|
||||
if not image:
|
||||
pecan.abort(404, 'Image not found')
|
||||
return
|
||||
return utils.format_nova_error(404, _('Image not found'))
|
||||
return {'image': self._construct_show_image_entry(context, image)}
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import rest
|
||||
|
||||
import tricircle.common.client as t_client
|
||||
import tricircle.common.context as t_context
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import utils
|
||||
|
||||
|
||||
class NetworkController(rest.RestController):
|
||||
|
@ -38,8 +39,7 @@ class NetworkController(rest.RestController):
|
|||
context = t_context.extract_context_from_environ()
|
||||
network = self.client.get_networks(context, _id)
|
||||
if not network:
|
||||
pecan.abort(404, 'Network not found')
|
||||
return
|
||||
return utils.format_nova_error(404, _('Network not found'))
|
||||
return {'network': self._construct_network_entry(network)}
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
|
|
|
@ -99,15 +99,14 @@ class ServerController(rest.RestController):
|
|||
mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
context, _id, constants.RT_SERVER)
|
||||
if not mappings:
|
||||
pecan.abort(404, 'Server not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Instance %s could not be found.') % _id)
|
||||
pod, bottom_id = mappings[0]
|
||||
client = self._get_client(pod['pod_name'])
|
||||
server = client.get_servers(context, bottom_id)
|
||||
if not server:
|
||||
self._remove_stale_mapping(context, _id)
|
||||
pecan.abort(404, 'Server not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Instance %s could not be found.') % _id)
|
||||
else:
|
||||
self._transform_network_name(server)
|
||||
return {'server': server}
|
||||
|
@ -123,18 +122,18 @@ class ServerController(rest.RestController):
|
|||
context = t_context.extract_context_from_environ()
|
||||
|
||||
if 'server' not in kw:
|
||||
pecan.abort(400, 'Request body not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('server is not set'))
|
||||
|
||||
if 'availability_zone' not in kw['server']:
|
||||
pecan.abort(400, 'Availability zone not set')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('availability zone is not set'))
|
||||
|
||||
pod, b_az = az_ag.get_pod_by_az_tenant(
|
||||
context, kw['server']['availability_zone'], self.project_id)
|
||||
if not pod:
|
||||
pecan.abort(400, 'No pod bound to availability zone')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
500, _('Pod not configured or scheduling failure'))
|
||||
|
||||
t_server_dict = kw['server']
|
||||
self._process_metadata_quota(context, t_server_dict)
|
||||
|
@ -155,12 +154,12 @@ class ServerController(rest.RestController):
|
|||
security_groups = []
|
||||
for sg in kw['server']['security_groups']:
|
||||
if 'name' not in sg:
|
||||
pecan.abort(404, 'Security group name not specified')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Invalid input for field/attribute'))
|
||||
if sg['name'] not in top_sg_map:
|
||||
pecan.abort(404,
|
||||
'Security group %s not found' % sg['name'])
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Unable to find security_group with name or id '
|
||||
'%s') % sg['name'])
|
||||
security_groups.append(sg['name'])
|
||||
t_sg_ids, b_sg_ids, is_news = self._handle_security_group(
|
||||
context, pod, top_sg_map, security_groups)
|
||||
|
@ -172,30 +171,32 @@ class ServerController(rest.RestController):
|
|||
network = top_client.get_networks(context,
|
||||
net_info['uuid'])
|
||||
if not network:
|
||||
pecan.abort(400, 'Network not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Network %s could not be '
|
||||
'found') % net_info['uuid'])
|
||||
|
||||
if not self._check_network_server_the_same_az(
|
||||
network, kw['server']['availability_zone']):
|
||||
pecan.abort(400, 'Network and server not in the same '
|
||||
'availability zone')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Network and server not in the same '
|
||||
'availability zone'))
|
||||
|
||||
subnets = top_client.list_subnets(
|
||||
context, [{'key': 'network_id',
|
||||
'comparator': 'eq',
|
||||
'value': network['id']}])
|
||||
if not subnets:
|
||||
pecan.abort(400, 'Network not contain subnets')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Network does not contain any subnets'))
|
||||
t_port_id, b_port_id = self._handle_network(
|
||||
context, pod, network, subnets,
|
||||
top_sg_ids=t_sg_ids, bottom_sg_ids=b_sg_ids)
|
||||
elif 'port' in net_info:
|
||||
port = top_client.get_ports(context, net_info['port'])
|
||||
if not port:
|
||||
pecan.abort(400, 'Port not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Port %s could not be '
|
||||
'found') % net_info['port'])
|
||||
t_port_id, b_port_id = self._handle_port(
|
||||
context, pod, port)
|
||||
server_body['networks'].append({'port': b_port_id})
|
||||
|
@ -749,7 +750,7 @@ class ServerController(rest.RestController):
|
|||
msg = str(e)
|
||||
LOG.exception(_LE('Quota exceeded %(msg)s'),
|
||||
{'msg': msg})
|
||||
pecan.abort(400, _('Quota exceeded %s') % msg)
|
||||
return utils.format_nova_error(400, _('Quota exceeded %s') % msg)
|
||||
|
||||
def _check_injected_file_quota(self, context, injected_files):
|
||||
"""Enforce quota limits on injected files.
|
||||
|
@ -796,15 +797,16 @@ class ServerController(rest.RestController):
|
|||
except t_exceptions.InvalidMetadata as e1:
|
||||
LOG.exception(_LE('Invalid metadata %(exception)s'),
|
||||
{'exception': str(e1)})
|
||||
pecan.abort(400, _('Invalid metadata'))
|
||||
return utils.format_nova_error(400, _('Invalid metadata'))
|
||||
except t_exceptions.InvalidMetadataSize as e2:
|
||||
LOG.exception(_LE('Invalid metadata size %(exception)s'),
|
||||
{'exception': str(e2)})
|
||||
pecan.abort(400, _('Invalid metadata size'))
|
||||
return utils.format_nova_error(400, _('Invalid metadata size'))
|
||||
except t_exceptions.MetadataLimitExceeded as e3:
|
||||
LOG.exception(_LE('Quota exceeded %(exception)s'),
|
||||
{'exception': str(e3)})
|
||||
pecan.abort(400, _('Quota exceeded in metadata'))
|
||||
return utils.format_nova_error(400,
|
||||
_('Quota exceeded in metadata'))
|
||||
|
||||
def _check_metadata_properties_quota(self, context, metadata=None):
|
||||
"""Enforce quota limits on metadata properties."""
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import rest
|
||||
import re
|
||||
|
@ -23,7 +22,9 @@ from oslo_log import log as logging
|
|||
import tricircle.common.client as t_client
|
||||
from tricircle.common import constants
|
||||
import tricircle.common.context as t_context
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common.i18n import _LE
|
||||
from tricircle.common import utils
|
||||
import tricircle.db.api as db_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -46,23 +47,23 @@ class VolumeController(rest.RestController):
|
|||
context = t_context.extract_context_from_environ()
|
||||
|
||||
if 'volumeAttachment' not in kw:
|
||||
pecan.abort(400, 'Request body not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('volumeAttachment is not set'))
|
||||
body = kw['volumeAttachment']
|
||||
if 'volumeId' not in body:
|
||||
pecan.abort(400, 'Volume not set')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Invalid input for field/attribute volumeAttachment'))
|
||||
|
||||
server_mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
context, self.server_id, constants.RT_SERVER)
|
||||
if not server_mappings:
|
||||
pecan.abort(404, 'Server not found')
|
||||
return
|
||||
return utils.format_nova_error(404, _('Instance %s could not be '
|
||||
'found.') % self.server_id)
|
||||
volume_mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
context, body['volumeId'], constants.RT_VOLUME)
|
||||
if not volume_mappings:
|
||||
pecan.abort(404, 'Volume not found')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
404, _('Volume %s could not be found') % body['volumeId'])
|
||||
|
||||
server_pod_name = server_mappings[0][0]['pod_name']
|
||||
volume_pod_name = volume_mappings[0][0]['pod_name']
|
||||
|
@ -74,8 +75,8 @@ class VolumeController(rest.RestController):
|
|||
'server_pod': server_pod_name,
|
||||
'volume': body['volumeId'],
|
||||
'volume_pod': volume_pod_name})
|
||||
pecan.abort(400, 'Server and volume not in the same pod')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('Server and volume not in the same pod'))
|
||||
|
||||
device = None
|
||||
if 'device' in body:
|
||||
|
@ -84,8 +85,9 @@ class VolumeController(rest.RestController):
|
|||
match = re.match('(^/dev/x{0,1}[a-z]{0,1}d{0,1})([a-z]+)[0-9]*$',
|
||||
device)
|
||||
if not match:
|
||||
pecan.abort(400, 'Invalid device path')
|
||||
return
|
||||
return utils.format_nova_error(
|
||||
400, _('The supplied device path (%s) is '
|
||||
'invalid.') % device)
|
||||
|
||||
client = self._get_client(server_pod_name)
|
||||
volume = client.action_server_volumes(
|
||||
|
|
|
@ -74,6 +74,9 @@ class ActionTest(unittest.TestCase):
|
|||
'resource_type': constants.RT_SERVER})
|
||||
return t_server_id
|
||||
|
||||
def _validate_error_code(self, res, code):
|
||||
self.assertEqual(code, res[res.keys()[0]]['code'])
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_action_not_supported(self, mock_context):
|
||||
|
@ -81,9 +84,7 @@ class ActionTest(unittest.TestCase):
|
|||
|
||||
body = {'unsupported_action': ''}
|
||||
res = self.controller.post(**body)
|
||||
self.assertEqual('Server action not supported',
|
||||
res['Error']['message'])
|
||||
self.assertEqual(400, res['Error']['code'])
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
|
@ -92,8 +93,7 @@ class ActionTest(unittest.TestCase):
|
|||
|
||||
body = {'os-start': ''}
|
||||
res = self.controller.post(**body)
|
||||
self.assertEqual('Server not found', res['Error']['message'])
|
||||
self.assertEqual(404, res['Error']['code'])
|
||||
self._validate_error_code(res, 404)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(client.Client, 'action_resources')
|
||||
|
@ -109,27 +109,17 @@ class ActionTest(unittest.TestCase):
|
|||
msg='Server operation forbidden')
|
||||
body = {'os-start': ''}
|
||||
res = self.controller.post(**body)
|
||||
# this is the message of HTTPForbiddenError exception
|
||||
self.assertEqual('Server operation forbidden', res['Error']['message'])
|
||||
# this is the code of HTTPForbiddenError exception
|
||||
self.assertEqual(403, res['Error']['code'])
|
||||
self._validate_error_code(res, 403)
|
||||
|
||||
mock_action.side_effect = exceptions.ServiceUnavailable
|
||||
body = {'os-start': ''}
|
||||
res = self.controller.post(**body)
|
||||
# this is the message of ServiceUnavailable exception
|
||||
self.assertEqual('The service is unavailable', res['Error']['message'])
|
||||
# code is 500 by default
|
||||
self.assertEqual(500, res['Error']['code'])
|
||||
self._validate_error_code(res, 500)
|
||||
|
||||
mock_action.side_effect = Exception
|
||||
body = {'os-start': ''}
|
||||
res = self.controller.post(**body)
|
||||
# use default message if exception's message is empty
|
||||
self.assertEqual('Action os-start on server %s fails' % t_server_id,
|
||||
res['Error']['message'])
|
||||
# code is 500 by default
|
||||
self.assertEqual(500, res['Error']['code'])
|
||||
self._validate_error_code(res, 500)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(client.Client, 'action_resources')
|
||||
|
|
|
@ -26,7 +26,6 @@ from oslo_utils import uuidutils
|
|||
from tricircle.common import constants
|
||||
from tricircle.common import context
|
||||
import tricircle.common.exceptions as t_exceptions
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import lock_handle
|
||||
from tricircle.common import xrpcapi
|
||||
from tricircle.db import api
|
||||
|
@ -319,6 +318,9 @@ class ServerTest(unittest.TestCase):
|
|||
b_pods.append(b_pod)
|
||||
return t_pod, b_pods
|
||||
|
||||
def _validate_error_code(self, res, code):
|
||||
self.assertEqual(code, res[res.keys()[0]]['code'])
|
||||
|
||||
def test_get_or_create_route(self):
|
||||
t_pod, b_pod = self._prepare_pod()
|
||||
route, is_own = self.controller._get_or_create_route(
|
||||
|
@ -378,7 +380,7 @@ class ServerTest(unittest.TestCase):
|
|||
t_pod, b_pod = self._prepare_pod()
|
||||
port = {'id': 'top_port_id'}
|
||||
body = {'port': {'name': 'top_port_id'}}
|
||||
_, bottom_port_id = self.controller._prepare_neutron_element(
|
||||
is_new, bottom_port_id = self.controller._prepare_neutron_element(
|
||||
self.context, b_pod, port, 'port', body)
|
||||
mappings = api.get_bottom_mappings_by_top_id(self.context,
|
||||
'top_port_id', 'port')
|
||||
|
@ -526,10 +528,9 @@ class ServerTest(unittest.TestCase):
|
|||
self._test_handle_network_dhcp_port('10.0.0.4')
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(pecan, 'abort')
|
||||
@patch.object(FakeClient, 'create_servers')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_post_with_network_az(self, mock_ctx, mock_create, mock_abort):
|
||||
def test_post_with_network_az(self, mock_ctx, mock_create):
|
||||
t_pod, b_pod = self._prepare_pod()
|
||||
top_net_id = 'top_net_id'
|
||||
top_subnet_id = 'top_subnet_id'
|
||||
|
@ -586,16 +587,13 @@ class ServerTest(unittest.TestCase):
|
|||
|
||||
# update top net for test purpose, wrong az
|
||||
TOP_NETS[0]['availability_zone_hints'] = ['fake_az']
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
# update top net for test purpose, correct az and wrong az
|
||||
TOP_NETS[0]['availability_zone_hints'] = ['b_az', 'fake_az']
|
||||
self.controller.post(**body)
|
||||
|
||||
msg = 'Network and server not in the same availability zone'
|
||||
# abort two times
|
||||
calls = [mock.call(400, msg), mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(FakeClient, 'create_servers')
|
||||
|
@ -979,8 +977,8 @@ class ServerTest(unittest.TestCase):
|
|||
res['Error']['message'])
|
||||
self.assertEqual(404, res['Error']['code'])
|
||||
|
||||
@patch.object(pecan, 'abort')
|
||||
def test_process_injected_file_quota(self, mock_abort):
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
def test_process_injected_file_quota(self):
|
||||
ctx = self.context.elevated()
|
||||
|
||||
def _update_default_quota(num1, len1, len2):
|
||||
|
@ -1017,11 +1015,8 @@ class ServerTest(unittest.TestCase):
|
|||
self.controller._check_injected_file_quota,
|
||||
ctx, injected_files)
|
||||
|
||||
self.controller._process_injected_file_quota(ctx, t_server_dict)
|
||||
msg = _('Quota exceeded %s') % \
|
||||
t_exceptions.OnsetFileLimitExceeded.message
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_injected_file_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
_update_default_quota(len(injected_files),
|
||||
max_path + 1,
|
||||
|
@ -1035,11 +1030,8 @@ class ServerTest(unittest.TestCase):
|
|||
self.controller._check_injected_file_quota,
|
||||
ctx, injected_files)
|
||||
|
||||
self.controller._process_injected_file_quota(ctx, t_server_dict)
|
||||
msg = _('Quota exceeded %s') % \
|
||||
t_exceptions.OnsetFilePathLimitExceeded.message
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_injected_file_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
_update_default_quota(len(injected_files) + 1,
|
||||
max_path,
|
||||
|
@ -1053,19 +1045,16 @@ class ServerTest(unittest.TestCase):
|
|||
self.controller._check_injected_file_quota,
|
||||
ctx, injected_files)
|
||||
|
||||
self.controller._process_injected_file_quota(ctx, t_server_dict)
|
||||
msg = _('Quota exceeded %s') % \
|
||||
t_exceptions.OnsetFileContentLimitExceeded.message
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_injected_file_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
_update_default_quota(len(injected_files) + 1,
|
||||
max_path + 1,
|
||||
max_content)
|
||||
self.controller._check_injected_file_quota(ctx, injected_files)
|
||||
|
||||
@patch.object(pecan, 'abort')
|
||||
def test_process_metadata_quota(self, mock_abort):
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
def test_process_metadata_quota(self):
|
||||
ctx = self.context.elevated()
|
||||
|
||||
def _update_default_quota(num):
|
||||
|
@ -1092,10 +1081,8 @@ class ServerTest(unittest.TestCase):
|
|||
self.assertRaises(t_exceptions.InvalidMetadata,
|
||||
self.controller._check_metadata_properties_quota,
|
||||
ctx, meta_data_items)
|
||||
self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
msg = _('Invalid metadata')
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
meta_data_items['A'] = '1'
|
||||
_update_default_quota(len(meta_data_items))
|
||||
|
@ -1110,10 +1097,8 @@ class ServerTest(unittest.TestCase):
|
|||
self.controller._check_metadata_properties_quota,
|
||||
ctx, meta_data_items)
|
||||
|
||||
self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
msg = _('Quota exceeded in metadata')
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
_update_default_quota(len(meta_data_items) + 1)
|
||||
|
||||
|
@ -1125,10 +1110,8 @@ class ServerTest(unittest.TestCase):
|
|||
self.controller._check_metadata_properties_quota,
|
||||
ctx, meta_data_items)
|
||||
|
||||
self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
msg = _('Invalid metadata size')
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
meta_data_items['C'] = '3'
|
||||
meta_data_items[string_exceed_MAX_METADATA_LEGNGTH] = '4'
|
||||
|
@ -1136,10 +1119,8 @@ class ServerTest(unittest.TestCase):
|
|||
self.controller._check_metadata_properties_quota,
|
||||
ctx, meta_data_items)
|
||||
|
||||
self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
msg = _('Invalid metadata size')
|
||||
calls = [mock.call(400, msg)]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller._process_metadata_quota(ctx, t_server_dict)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
|
|
|
@ -29,6 +29,13 @@ from tricircle.db import models
|
|||
from tricircle.nova_apigw.controllers import volume
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __new__(cls, code=500):
|
||||
cls.status = code
|
||||
cls.status_code = code
|
||||
return super(FakeResponse, cls).__new__(cls)
|
||||
|
||||
|
||||
class FakeVolume(object):
|
||||
def to_dict(self):
|
||||
pass
|
||||
|
@ -60,10 +67,13 @@ class VolumeTest(unittest.TestCase):
|
|||
b_pods.append(b_pod)
|
||||
return t_pod, b_pods
|
||||
|
||||
@patch.object(pecan, 'abort')
|
||||
def _validate_error_code(self, res, code):
|
||||
self.assertEqual(code, res[res.keys()[0]]['code'])
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(client.Client, 'action_resources')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_attach_volume(self, mock_context, mock_action, mock_abort):
|
||||
def test_attach_volume(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = FakeVolume()
|
||||
|
||||
|
@ -112,36 +122,40 @@ class VolumeTest(unittest.TestCase):
|
|||
|
||||
# failure case, bad request
|
||||
body = {'volumeAttachment': {'volumeId': t_volume2_id}}
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
body = {'fakePara': ''}
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
body = {'volumeAttachment': {}}
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
# each part of path should not start with digit
|
||||
body = {'volumeAttachment': {'volumeId': t_volume1_id,
|
||||
'device': '/dev/001disk'}}
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
# the first part should be "dev", and only two parts are allowed
|
||||
body = {'volumeAttachment': {'volumeId': t_volume1_id,
|
||||
'device': '/dev/vdb/disk'}}
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
body = {'volumeAttachment': {'volumeId': t_volume1_id,
|
||||
'device': '/disk/vdb'}}
|
||||
self.controller.post(**body)
|
||||
calls = [mock.call(400, 'Server and volume not in the same pod'),
|
||||
mock.call(400, 'Request body not found'),
|
||||
mock.call(400, 'Volume not set'),
|
||||
mock.call(400, 'Invalid device path'),
|
||||
mock.call(400, 'Invalid device path'),
|
||||
mock.call(400, 'Invalid device path')]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 400)
|
||||
|
||||
# failure case, resource not found
|
||||
body = {'volumeAttachment': {'volumeId': 'fake_volume_id'}}
|
||||
self.controller.post(**body)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 404)
|
||||
|
||||
self.controller.server_id = 'fake_server_id'
|
||||
body = {'volumeAttachment': {'volumeId': t_volume1_id}}
|
||||
self.controller.post(**body)
|
||||
calls = [mock.call(404, 'Volume not found'),
|
||||
mock.call(404, 'Server not found')]
|
||||
mock_abort.assert_has_calls(calls)
|
||||
res = self.controller.post(**body)
|
||||
self._validate_error_code(res, 404)
|
||||
|
|
Loading…
Reference in New Issue