From 407c17b38a05580ea2117e215efa86c541b584f6 Mon Sep 17 00:00:00 2001 From: Justin Pomeroy Date: Thu, 10 Jul 2014 11:28:06 -0500 Subject: [PATCH] Tolerate service catalog and endpoint connection errors This is a fix for the issues encountered when a service is not configured or a service endpoint is not reachable. This fix will add tolerance for these errors so that an error message is displayed but the dashboard page will still load, not an error page. This makes it easier for the user to recover by allowing them to go to a different page, select a different region, or logout. This makes sense in many cases such as when a region only contains an image service endpoint, or when a single endpoint is not reachable for whatever reason. It also adds permissions to the panels that require compute or image services so that the dashboard will not display them if the service is not configured. To test these changes you will need to set up your keystone service catalog so that not all services are available in all regions, or some of the service endpoints are not reachable. Change-Id: Ie04699d1fb1d4db13a7f4dcf1bdfd23bf21aab80 Closes-Bug: 1323811 Closes-Bug: 1207636 --- horizon/exceptions.py | 2 +- horizon/tabs/base.py | 12 ++++++++- openstack_dashboard/api/cinder.py | 2 +- .../dashboards/admin/aggregates/panel.py | 1 + .../dashboards/admin/flavors/panel.py | 1 + .../dashboards/admin/hypervisors/panel.py | 2 +- .../dashboards/admin/images/panel.py | 1 + .../dashboards/admin/info/tabs.py | 24 +++++++++++------ .../dashboards/admin/instances/panel.py | 2 +- .../project/access_and_security/tabs.py | 3 +++ .../dashboards/project/images/panel.py | 1 + .../dashboards/project/instances/panel.py | 1 + openstack_dashboard/exceptions.py | 2 ++ openstack_dashboard/usage/views.py | 27 +++++++++++++------ 14 files changed, 60 insertions(+), 21 deletions(-) diff --git a/horizon/exceptions.py b/horizon/exceptions.py index 9c7879f7ae..de2b72e282 100644 --- a/horizon/exceptions.py +++ b/horizon/exceptions.py @@ -197,7 +197,7 @@ class HandledException(HorizonException): UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized']) NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found']) -RECOVERABLE = (AlreadyExists, Conflict, NotAvailable) +RECOVERABLE = (AlreadyExists, Conflict, NotAvailable, ServiceCatalogException) RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable']) diff --git a/horizon/tabs/base.py b/horizon/tabs/base.py index e9195a5613..4599641c38 100644 --- a/horizon/tabs/base.py +++ b/horizon/tabs/base.py @@ -238,11 +238,17 @@ class Tab(html.HTMLElement): Read-only access to determine whether or not this tab's data should be loaded immediately. + + .. attribute:: permissions + + A list of permission names which this tab requires in order to be + displayed. Defaults to an empty list (``[]``). """ name = None slug = None preload = True _active = None + permissions = [] def __init__(self, tab_group, request=None): super(Tab, self).__init__() @@ -255,12 +261,16 @@ class Tab(html.HTMLElement): self.tab_group = tab_group self.request = request if request: - self._allowed = self.allowed(request) + self._allowed = self.allowed(request) and ( + self._has_permissions(request)) self._enabled = self.enabled(request) def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.slug) + def _has_permissions(self, request): + return request.user.has_perms(self.permissions) + def is_active(self): """Method to access whether or not this tab is the active tab.""" if self._active is None: diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index bb54045d83..2c2963753a 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -130,7 +130,7 @@ def cinderclient(request): cinder_url = base.url_for(request, 'volume') except exceptions.ServiceCatalogException: LOG.debug('no volume service configured.') - return None + raise LOG.debug('cinderclient connection created using token "%s" and url "%s"' % (request.user.token.id, cinder_url)) c = api_version['client'].Client(request.user.username, diff --git a/openstack_dashboard/dashboards/admin/aggregates/panel.py b/openstack_dashboard/dashboards/admin/aggregates/panel.py index e6e7c42aab..2477295e4c 100644 --- a/openstack_dashboard/dashboards/admin/aggregates/panel.py +++ b/openstack_dashboard/dashboards/admin/aggregates/panel.py @@ -20,6 +20,7 @@ from openstack_dashboard.dashboards.admin import dashboard class Aggregates(horizon.Panel): name = _("Host Aggregates") slug = 'aggregates' + permissions = ('openstack.services.compute',) dashboard.Admin.register(Aggregates) diff --git a/openstack_dashboard/dashboards/admin/flavors/panel.py b/openstack_dashboard/dashboards/admin/flavors/panel.py index d13248fe3f..b2d0e4e8d7 100644 --- a/openstack_dashboard/dashboards/admin/flavors/panel.py +++ b/openstack_dashboard/dashboards/admin/flavors/panel.py @@ -26,6 +26,7 @@ from openstack_dashboard.dashboards.admin import dashboard class Flavors(horizon.Panel): name = _("Flavors") slug = 'flavors' + permissions = ('openstack.services.compute',) dashboard.Admin.register(Flavors) diff --git a/openstack_dashboard/dashboards/admin/hypervisors/panel.py b/openstack_dashboard/dashboards/admin/hypervisors/panel.py index c47a7d0c1e..7e5d27533c 100644 --- a/openstack_dashboard/dashboards/admin/hypervisors/panel.py +++ b/openstack_dashboard/dashboards/admin/hypervisors/panel.py @@ -21,7 +21,7 @@ from openstack_dashboard.dashboards.admin import dashboard class Hypervisors(horizon.Panel): name = _("Hypervisors") slug = 'hypervisors' - permissions = ('openstack.roles.admin',) + permissions = ('openstack.roles.admin', 'openstack.services.compute') dashboard.Admin.register(Hypervisors) diff --git a/openstack_dashboard/dashboards/admin/images/panel.py b/openstack_dashboard/dashboards/admin/images/panel.py index 2b525f6f0a..5983aa6a4c 100644 --- a/openstack_dashboard/dashboards/admin/images/panel.py +++ b/openstack_dashboard/dashboards/admin/images/panel.py @@ -26,6 +26,7 @@ from openstack_dashboard.dashboards.admin import dashboard class Images(horizon.Panel): name = _("Images") slug = 'images' + permissions = ('openstack.services.image',) dashboard.Admin.register(Images) diff --git a/openstack_dashboard/dashboards/admin/info/tabs.py b/openstack_dashboard/dashboards/admin/info/tabs.py index 397bea69d8..9bcd25824e 100644 --- a/openstack_dashboard/dashboards/admin/info/tabs.py +++ b/openstack_dashboard/dashboards/admin/info/tabs.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -49,6 +50,7 @@ class NovaServicesTab(tabs.TableTab): name = _("Compute Services") slug = "nova_services" template_name = constants.INFO_DETAIL_TEMPLATE_NAME + permissions = ('openstack.services.compute',) def get_nova_services_data(self): try: @@ -56,8 +58,8 @@ class NovaServicesTab(tabs.TableTab): except Exception: msg = _('Unable to get nova services list.') exceptions.check_message(["Connection", "refused"], msg) - raise - + exceptions.handle(self.request, msg) + services = [] return services @@ -66,6 +68,7 @@ class CinderServicesTab(tabs.TableTab): name = _("Block Storage Services") slug = "cinder_services" template_name = constants.INFO_DETAIL_TEMPLATE_NAME + permissions = ('openstack.services.volume',) def get_cinder_services_data(self): try: @@ -73,8 +76,8 @@ class CinderServicesTab(tabs.TableTab): except Exception: msg = _('Unable to get cinder services list.') exceptions.check_message(["Connection", "refused"], msg) - raise - + exceptions.handle(self.request, msg) + services = [] return services @@ -85,8 +88,12 @@ class NetworkAgentsTab(tabs.TableTab): template_name = constants.INFO_DETAIL_TEMPLATE_NAME def allowed(self, request): - return (base.is_service_enabled(request, 'network') and - neutron.is_agent_extension_supported(request)) + try: + return (base.is_service_enabled(request, 'network') and + neutron.is_agent_extension_supported(request)) + except Exception: + exceptions.handle(request, _('Unable to get network agents info.')) + return False def get_network_agents_data(self): try: @@ -94,8 +101,8 @@ class NetworkAgentsTab(tabs.TableTab): except Exception: msg = _('Unable to get network agents list.') exceptions.check_message(["Connection", "refused"], msg) - raise - + exceptions.handle(self.request, msg) + agents = [] return agents @@ -104,6 +111,7 @@ class DefaultQuotasTab(tabs.TableTab): name = _("Default Quotas") slug = "quotas" template_name = constants.INFO_DETAIL_TEMPLATE_NAME + permissions = ('openstack.services.compute',) def get_quotas_data(self): request = self.tab_group.request diff --git a/openstack_dashboard/dashboards/admin/instances/panel.py b/openstack_dashboard/dashboards/admin/instances/panel.py index 3dcb1bce63..39a0047a7a 100644 --- a/openstack_dashboard/dashboards/admin/instances/panel.py +++ b/openstack_dashboard/dashboards/admin/instances/panel.py @@ -26,7 +26,7 @@ from openstack_dashboard.dashboards.admin import dashboard class Instances(horizon.Panel): name = _("Instances") slug = 'instances' - permissions = ('openstack.roles.admin',) + permissions = ('openstack.roles.admin', 'openstack.services.compute') dashboard.Admin.register(Instances) diff --git a/openstack_dashboard/dashboards/project/access_and_security/tabs.py b/openstack_dashboard/dashboards/project/access_and_security/tabs.py index 06e19a4a8c..00948460c2 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/tabs.py +++ b/openstack_dashboard/dashboards/project/access_and_security/tabs.py @@ -42,6 +42,7 @@ class SecurityGroupsTab(tabs.TableTab): name = _("Security Groups") slug = "security_groups_tab" template_name = "horizon/common/_detail_table.html" + permissions = ('openstack.services.compute',) def get_security_groups_data(self): try: @@ -58,6 +59,7 @@ class KeypairsTab(tabs.TableTab): name = _("Key Pairs") slug = "keypairs_tab" template_name = "horizon/common/_detail_table.html" + permissions = ('openstack.services.compute',) def get_keypairs_data(self): try: @@ -74,6 +76,7 @@ class FloatingIPsTab(tabs.TableTab): name = _("Floating IPs") slug = "floating_ips_tab" template_name = "horizon/common/_detail_table.html" + permissions = ('openstack.services.compute',) def get_floating_ips_data(self): try: diff --git a/openstack_dashboard/dashboards/project/images/panel.py b/openstack_dashboard/dashboards/project/images/panel.py index bf5bf41e3c..7203f506d3 100644 --- a/openstack_dashboard/dashboards/project/images/panel.py +++ b/openstack_dashboard/dashboards/project/images/panel.py @@ -23,6 +23,7 @@ from openstack_dashboard.dashboards.project import dashboard class Images(horizon.Panel): name = _("Images") slug = 'images' + permissions = ('openstack.services.image',) dashboard.Project.register(Images) diff --git a/openstack_dashboard/dashboards/project/instances/panel.py b/openstack_dashboard/dashboards/project/instances/panel.py index a590950703..acade3c519 100644 --- a/openstack_dashboard/dashboards/project/instances/panel.py +++ b/openstack_dashboard/dashboards/project/instances/panel.py @@ -22,6 +22,7 @@ from openstack_dashboard.dashboards.project import dashboard class Instances(horizon.Panel): name = _("Instances") slug = 'instances' + permissions = ('openstack.services.compute',) dashboard.Project.register(Instances) diff --git a/openstack_dashboard/exceptions.py b/openstack_dashboard/exceptions.py index 89e7ceb59c..02177f1948 100644 --- a/openstack_dashboard/exceptions.py +++ b/openstack_dashboard/exceptions.py @@ -22,6 +22,7 @@ from heatclient import exc as heatclient from keystoneclient import exceptions as keystoneclient from neutronclient.common import exceptions as neutronclient from novaclient import exceptions as novaclient +from requests import exceptions as requests from saharaclient.api import base as saharaclient from swiftclient import client as swiftclient from troveclient import exceptions as troveclient @@ -76,4 +77,5 @@ RECOVERABLE = ( heatclient.HTTPException, troveclient.ClientException, saharaclient.APIException, + requests.RequestException, ) diff --git a/openstack_dashboard/usage/views.py b/openstack_dashboard/usage/views.py index 6d04753a8c..fe6ff8ff73 100644 --- a/openstack_dashboard/usage/views.py +++ b/openstack_dashboard/usage/views.py @@ -10,6 +10,8 @@ # 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 openstack_dashboard import api from openstack_dashboard.usage import base @@ -38,20 +40,29 @@ class UsageView(tables.DataTableView): return "text/html" def get_data(self): - project_id = self.kwargs.get('project_id', self.request.user.tenant_id) - self.usage = self.usage_class(self.request, project_id) - self.usage.summarize(*self.usage.get_date_range()) - self.usage.get_limits() - self.kwargs['usage'] = self.usage - return self.usage.usage_list + try: + project_id = self.kwargs.get('project_id', + self.request.user.tenant_id) + self.usage = self.usage_class(self.request, project_id) + self.usage.summarize(*self.usage.get_date_range()) + self.usage.get_limits() + self.kwargs['usage'] = self.usage + return self.usage.usage_list + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve usage information.')) + return [] def get_context_data(self, **kwargs): context = super(UsageView, self).get_context_data(**kwargs) context['table'].kwargs['usage'] = self.usage context['form'] = self.usage.form context['usage'] = self.usage - context['simple_tenant_usage_enabled'] = \ - api.nova.extension_supported('SimpleTenantUsage', self.request) + try: + context['simple_tenant_usage_enabled'] = \ + api.nova.extension_supported('SimpleTenantUsage', self.request) + except Exception: + context['simple_tenant_usage_enabled'] = True return context def render_to_response(self, context, **response_kwargs):