Merge "Error handling in the API"

This commit is contained in:
Jenkins 2014-03-04 18:08:16 +00:00 committed by Gerrit Code Review
commit 73d72ebd0e
10 changed files with 109 additions and 117 deletions

View File

@ -15,18 +15,18 @@ import django.conf
import heatclient
import logging
from django.utils.translation import ugettext_lazy as _
from horizon.utils import memoized
from novaclient.v1_1.contrib import baremetal
from tuskarclient.v1 import client as tuskar_client
from openstack_dashboard.api import base
from openstack_dashboard.api import glance
from openstack_dashboard.api import heat
from openstack_dashboard.api import nova
from openstack_dashboard.test.test_data import utils
from tuskarclient.v1 import client as tuskar_client
from tuskar_ui.cached_property import cached_property # noqa
from tuskar_ui.handle_errors import handle_errors # noqa
from tuskar_ui.test.test_data import tuskar_data
LOG = logging.getLogger(__name__)
@ -133,12 +133,14 @@ class NodeProfile(object):
return cls(nova.flavor_get(request, node_profile_id))
@classmethod
@handle_errors(_("Unable to retrieve node profile list."), [])
def list(cls, request):
return [cls(item) for item in nova.flavor_list(request)]
@staticmethod
@classmethod
@memoized.memoized
def list_deployed_ids(request):
@handle_errors(_("Unable to retrieve existing servers list."), [])
def list_deployed_ids(cls, request):
"""Get and memoize ID's of deployed node profiles."""
servers = nova.server_list(request)[0]
return set(server.flavor['id'] for server in servers)
@ -199,6 +201,7 @@ class Overcloud(base.APIResourceWrapper):
return [cls(oc, request=request) for oc in ocs]
@classmethod
@handle_errors(_("Unable to retrieve deployment"))
def get(cls, request, overcloud_id):
"""Return the Tuskar Overcloud that matches the ID
@ -489,6 +492,7 @@ class Node(base.APIResourceWrapper):
return cls(node)
@classmethod
@handle_errors(_("Unable to retrieve node"))
def get(cls, request, uuid):
"""Return the Node in Ironic that matches the ID
@ -542,6 +546,7 @@ class Node(base.APIResourceWrapper):
return cls(node, instance=server, request=request)
@classmethod
@handle_errors(_("Unable to retrieve nodes"), [])
def list(cls, request, associated=None):
"""Return a list of Nodes in Ironic
@ -799,6 +804,7 @@ class OvercloudRole(base.APIResourceWrapper):
_attrs = ('id', 'name', 'description', 'image_name', 'flavor_id')
@classmethod
@handle_errors(_("Unable to retrieve overcloud roles"), [])
def list(cls, request):
"""Return a list of Overcloud Roles in Tuskar
@ -813,6 +819,7 @@ class OvercloudRole(base.APIResourceWrapper):
return [cls(role) for role in roles]
@classmethod
@handle_errors(_("Unable to retrieve overcloud role"))
def get(cls, request, role_id):
"""Return the Tuskar OvercloudRole that matches the ID

View File

@ -0,0 +1,56 @@
# -*- coding: utf8 -*-
#
# 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 functools
import horizon.exceptions
def handle_errors(error_message, error_default=None):
"""A decorator for adding default error handling to API calls.
It wraps the original method in a try-except block, with horizon's
error handling added.
Note: it should only be used on methods that take request as the first
(second after self or cls) argument.
The decorated method accepts a number of additional parameters:
:param _error_handle: whether to handle the errors in this call
:param _error_message: override the error message
:param _error_default: override the default value returned on error
:param _error_redirect: specify a redirect url for errors
:param _error_ignore: ignore known errors
"""
def decorator(func):
@functools.wraps(func)
def wrapper(self, request, *args, **kwargs):
_error_handle = kwargs.pop('_error_handle', True)
_error_message = kwargs.pop('_error_message', error_message)
_error_default = kwargs.pop('_error_default', error_default)
_error_redirect = kwargs.pop('_error_redirect', None)
_error_ignore = kwargs.pop('_error_ignore', False)
if not _error_handle:
return func(self, request, *args, **kwargs)
try:
return func(self, request, *args, **kwargs)
except Exception:
horizon.exceptions.handle(request, _error_message,
ignore=_error_ignore,
redirect=_error_redirect)
return _error_default
wrapper.wrapped = func
return wrapper
return decorator

View File

@ -14,7 +14,6 @@
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tables
from openstack_dashboard.dashboards.admin.flavors \
@ -46,13 +45,9 @@ class DeleteNodeProfile(flavor_tables.DeleteFlavor):
:type datum: tuskar_ui.api.NodeProfile
"""
if datum is not None:
try:
deployed_profiles = api.NodeProfile.list_deployed_ids(request)
except Exception:
msg = _('Unable to retrieve existing servers list.')
exceptions.handle(request, msg)
return False
if datum.id in deployed_profiles:
deployed_profiles = api.NodeProfile.list_deployed_ids(
request, _error_default=None)
if deployed_profiles is None or datum.id in deployed_profiles:
return False
return super(DeleteNodeProfile, self).allowed(request, datum)

View File

@ -12,9 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tables
from horizon import workflows
@ -30,12 +27,7 @@ class IndexView(tables.DataTableView):
template_name = 'infrastructure/node_profiles/index.html'
def get_data(self):
try:
node_profiles = api.NodeProfile.list(self.request)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve node profile list.'))
return []
node_profiles = api.NodeProfile.list(self.request)
node_profiles.sort(key=lambda np: (np.vcpus, np.ram, np.disk))
return node_profiles

View File

@ -17,7 +17,6 @@
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from tuskar_ui import api
@ -30,14 +29,8 @@ class OverviewTab(tabs.Tab):
template_name = ("infrastructure/nodes/_overview.html")
def get_context_data(self, request):
try:
free_nodes = api.Node.list(request, associated=False)
deployed_nodes = api.Node.list(request, associated=True)
except Exception:
free_nodes = []
deployed_nodes = []
exceptions.handle(request,
_('Unable to retrieve nodes.'))
free_nodes = api.Node.list(request, associated=False)
deployed_nodes = api.Node.list(request, associated=True)
free_nodes_down = [node for node in free_nodes
if node.power_state != 'on']
@ -67,25 +60,14 @@ class DeployedTab(tabs.TableTab):
template_name = ("horizon/common/_detail_table.html")
def get_items_count(self):
try:
deployed_nodes_count = len(api.Node.list(self.request,
associated=True))
except Exception:
deployed_nodes_count = 0
exceptions.handle(self.request,
_('Unable to retrieve deployed nodes count.'))
deployed_nodes_count = len(api.Node.list(self.request,
associated=True))
return deployed_nodes_count
def get_deployed_nodes_data(self):
try:
deployed_nodes = api.Node.list(self.request, associated=True)
except Exception:
deployed_nodes = []
redirect = urlresolvers.reverse(
'horizon:infrastructure:nodes:index')
exceptions.handle(self.request,
_('Unable to retrieve deployed nodes.'),
redirect=redirect)
redirect = urlresolvers.reverse('horizon:infrastructure:nodes:index')
deployed_nodes = api.Node.list(self.request, associated=True,
_error_redirect=redirect)
return deployed_nodes
@ -97,25 +79,14 @@ class FreeTab(tabs.TableTab):
template_name = ("horizon/common/_detail_table.html")
def get_items_count(self):
try:
free_nodes_count = len(api.Node.list(self.request,
associated=False))
except Exception:
free_nodes_count = "0"
exceptions.handle(self.request,
_('Unable to retrieve free nodes count.'))
free_nodes_count = len(api.Node.list(self.request,
associated=False))
return free_nodes_count
def get_free_nodes_data(self):
try:
free_nodes = api.Node.list(self.request, associated=False)
except Exception:
free_nodes = []
redirect = urlresolvers.reverse(
'horizon:infrastructure:nodes:index')
exceptions.handle(self.request,
_('Unable to retrieve free nodes.'),
redirect=redirect)
redirect = urlresolvers.reverse('horizon:infrastructure:nodes:index')
free_nodes = api.Node.list(self.request, associated=False,
_error_redirect=redirect)
return free_nodes

View File

@ -18,6 +18,7 @@ from mock import patch, call # noqa
from openstack_dashboard.test.test_data import utils
from tuskar_ui import api as api
from tuskar_ui.handle_errors import handle_errors # noqa
from tuskar_ui.test import helpers as test
from tuskar_ui.test.test_data import tuskar_data
@ -30,6 +31,10 @@ tuskar_data.data(TEST_DATA)
class NodesTests(test.BaseAdminViewTests):
@handle_errors("Error!", [])
def _raise_tuskar_exception(self, request, *args, **kwargs):
raise self.exceptions.tuskar
def test_index_get(self):
with patch('tuskar_ui.api.Node', **{
@ -65,10 +70,10 @@ class NodesTests(test.BaseAdminViewTests):
def test_free_nodes_list_exception(self):
with patch('tuskar_ui.api.Node', **{
'spec_set': ['list'],
'list.side_effect': self.exceptions.tuskar,
'list.side_effect': self._raise_tuskar_exception,
}) as mock:
res = self.client.get(INDEX_URL + '?tab=nodes__free')
self.assertEqual(mock.list.call_count, 2)
self.assertEqual(mock.list.call_count, 3)
self.assertRedirectsNoFollow(res, INDEX_URL)
@ -101,10 +106,10 @@ class NodesTests(test.BaseAdminViewTests):
with patch('tuskar_ui.api.Node', **{
'spec_set': ['list', 'instance'],
'instance': instance,
'list.side_effect': self.exceptions.tuskar,
'list.side_effect': self._raise_tuskar_exception,
}) as mock:
res = self.client.get(INDEX_URL + '?tab=nodes__deployed')
self.assertEqual(mock.list.call_count, 2)
self.assertEqual(mock.list.call_count, 3)
self.assertRedirectsNoFollow(res, INDEX_URL)
@ -197,7 +202,7 @@ class NodesTests(test.BaseAdminViewTests):
def test_node_detail_exception(self):
with patch('tuskar_ui.api.Node', **{
'spec_set': ['get'],
'get.side_effect': self.exceptions.tuskar,
'get.side_effect': self._raise_tuskar_exception,
}) as mock:
res = self.client.get(
urlresolvers.reverse(DETAIL_VIEW, args=('no-such-node',))

View File

@ -13,9 +13,7 @@
# under the License.
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms as horizon_forms
from horizon import tabs as horizon_tabs
from horizon import views as horizon_views
@ -30,23 +28,12 @@ class IndexView(horizon_tabs.TabbedTableView):
template_name = 'infrastructure/nodes/index.html'
def get_free_nodes_count(self):
try:
free_nodes_count = len(api.Node.list(self.request,
associated=False))
except Exception:
free_nodes_count = 0
exceptions.handle(self.request,
_('Unable to retrieve free nodes.'))
free_nodes_count = len(api.Node.list(self.request, associated=False))
return free_nodes_count
def get_deployed_nodes_count(self):
try:
deployed_nodes_count = len(api.Node.list(self.request,
associated=True))
except Exception:
deployed_nodes_count = 0
exceptions.handle(self.request,
_('Unable to retrieve deployed nodes.'))
deployed_nodes_count = len(api.Node.list(self.request,
associated=True))
return deployed_nodes_count
def get_context_data(self, **kwargs):
@ -81,14 +68,7 @@ class DetailView(horizon_views.APIView):
def get_data(self, request, context, *args, **kwargs):
node_uuid = kwargs.get('node_uuid')
try:
node = api.Node.get(request, node_uuid)
except Exception:
node = None
redirect = reverse_lazy('horizon:infrastructure:nodes:index')
msg = _('Unable to retrieve node with UUID "%s"') % node_uuid
exceptions.handle(request, msg, redirect=redirect)
redirect = reverse_lazy('horizon:infrastructure:nodes:index')
node = api.Node.get(request, node_uuid, _error_redirect=redirect)
context['node'] = node
return context

View File

@ -16,7 +16,6 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tabs
from tuskar_ui import api
@ -64,12 +63,7 @@ class OverviewTab(tabs.Tab):
def get_context_data(self, request, **kwargs):
overcloud = self.tab_group.kwargs['overcloud']
try:
roles = api.OvercloudRole.list(request)
except Exception:
roles = []
exceptions.handle(request,
_("Unable to retrieve overcloud roles."))
roles = api.OvercloudRole.list(request)
role_data = [_get_role_data(overcloud, role) for role in roles]
total = sum(d['node_count'] for d in role_data)
progress = 100 * sum(d.get('running_node_count', 0)

View File

@ -60,11 +60,11 @@ def _mock_overcloud(**kwargs):
'update',
],
'counts': [],
'create.side_effect': lambda *args: oc,
'create.side_effect': lambda *args, **kwargs: oc,
'dashboard_url': '',
'delete.return_value': None,
'get.side_effect': lambda *args: oc,
'get_the_overcloud.side_effect': lambda *args: oc,
'get.side_effect': lambda *args, **kwargs: oc,
'get_the_overcloud.side_effect': lambda *args, **kwargs: oc,
'id': 1,
'is_deployed': True,
'is_deploying': False,
@ -72,7 +72,7 @@ def _mock_overcloud(**kwargs):
'resources.return_value': [],
'stack_events': [],
'stack': stack,
'update.side_effect': lambda *args: oc,
'update.side_effect': lambda *args, **kwargs: oc,
}
params.update(kwargs)
with patch('tuskar_ui.api.Overcloud', **params) as Overcloud:
@ -379,7 +379,7 @@ class OvercloudTests(test.BaseAdminViewTests):
'image_name',
'flavor_id',
],
'get.side_effect': lambda *args: role,
'get.side_effect': lambda *args, **kwargs: role,
'name': 'Compute',
'description': '...',
'image_name': '',

View File

@ -16,7 +16,6 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.generic import base as base_views
from horizon import exceptions
import horizon.forms
from horizon import tables as horizon_tables
from horizon import tabs as horizon_tabs
@ -41,12 +40,8 @@ class OvercloudMixin(object):
if redirect is None:
redirect = reverse(INDEX_URL)
overcloud_id = self.kwargs['overcloud_id']
try:
overcloud = api.Overcloud.get(self.request, overcloud_id)
except Exception:
msg = _("Unable to retrieve deployment.")
exceptions.handle(self.request, msg, redirect=redirect)
overcloud = api.Overcloud.get(self.request, overcloud_id,
_error_redirect=redirect)
return overcloud
@ -54,11 +49,8 @@ class OvercloudRoleMixin(object):
@memoized.memoized
def get_role(self, redirect=None):
role_id = self.kwargs['role_id']
try:
role = api.OvercloudRole.get(self.request, role_id)
except Exception:
msg = _("Unable to retrieve overcloud role.")
exceptions.handle(self.request, msg, redirect=redirect)
role = api.OvercloudRole.get(self.request, role_id,
_error_redirect=redirect)
return role