From e76a1bc0dc0097f0e58272fc3f6c30b3ef690b29 Mon Sep 17 00:00:00 2001
From: Justin Pomeroy <jpomero@linux.vnet.ibm.com>
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.

Conflicts:
	horizon/exceptions.py
	openstack_dashboard/dashboards/admin/info/tabs.py
	openstack_dashboard/exceptions.py

Change-Id: Ie04699d1fb1d4db13a7f4dcf1bdfd23bf21aab80
Closes-Bug: 1323811
Closes-Bug: 1207636
(cherry picked from commit 407c17b38a05580ea2117e215efa86c541b584f6)
---
 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 +
 openstack_dashboard/dashboards/admin/info/tabs.py  | 18 +++++++++++----
 .../dashboards/admin/instances/panel.py            |  2 +-
 .../dashboards/project/access_and_security/tabs.py |  3 +++
 .../dashboards/project/images/panel.py             |  1 +
 .../dashboards/project/instances/panel.py          |  1 +
 openstack_dashboard/exceptions.py                  |  4 +++-
 openstack_dashboard/usage/views.py                 | 27 +++++++++++++++-------
 14 files changed, 58 insertions(+), 19 deletions(-)

diff --git a/horizon/exceptions.py b/horizon/exceptions.py
index 230e7f1..5adee58 100644
--- a/horizon/exceptions.py
+++ b/horizon/exceptions.py
@@ -193,7 +193,7 @@ class HandledException(HorizonException):
 
 UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized'])
 NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found'])
-RECOVERABLE = (AlreadyExists, Conflict,)
+RECOVERABLE = (AlreadyExists, Conflict, ServiceCatalogException)
 RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable'])
 
 
diff --git a/horizon/tabs/base.py b/horizon/tabs/base.py
index f9b5e44..b97a267 100644
--- a/horizon/tabs/base.py
+++ b/horizon/tabs/base.py
@@ -237,11 +237,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__()
@@ -254,12 +260,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 34e29b7..2c3b404 100644
--- a/openstack_dashboard/api/cinder.py
+++ b/openstack_dashboard/api/cinder.py
@@ -113,7 +113,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 e6e7c42..2477295 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 2f769b0..658fd98 100644
--- a/openstack_dashboard/dashboards/admin/flavors/panel.py
+++ b/openstack_dashboard/dashboards/admin/flavors/panel.py
@@ -28,6 +28,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 156702e..e001fea 100644
--- a/openstack_dashboard/dashboards/admin/hypervisors/panel.py
+++ b/openstack_dashboard/dashboards/admin/hypervisors/panel.py
@@ -23,7 +23,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 aa8d354..35bee05 100644
--- a/openstack_dashboard/dashboards/admin/images/panel.py
+++ b/openstack_dashboard/dashboards/admin/images/panel.py
@@ -28,6 +28,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 c3e2300..61f5621 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
@@ -48,6 +49,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:
@@ -55,8 +57,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
 
 
@@ -67,7 +69,12 @@ class NetworkAgentsTab(tabs.TableTab):
     template_name = constants.INFO_DETAIL_TEMPLATE_NAME
 
     def allowed(self, request):
-        return base.is_service_enabled(request, 'network')
+        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:
@@ -75,8 +82,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
 
 
@@ -85,6 +92,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 3e4fd51..9f3ab79 100644
--- a/openstack_dashboard/dashboards/admin/instances/panel.py
+++ b/openstack_dashboard/dashboards/admin/instances/panel.py
@@ -28,7 +28,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 1bab5ee..9dcc39e 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/tabs.py
+++ b/openstack_dashboard/dashboards/project/access_and_security/tabs.py
@@ -44,6 +44,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:
@@ -60,6 +61,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:
@@ -76,6 +78,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 1d24b7a..5670cb3 100644
--- a/openstack_dashboard/dashboards/project/images/panel.py
+++ b/openstack_dashboard/dashboards/project/images/panel.py
@@ -25,6 +25,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 c204973..b70e3d7 100644
--- a/openstack_dashboard/dashboards/project/instances/panel.py
+++ b/openstack_dashboard/dashboards/project/instances/panel.py
@@ -24,6 +24,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 949a0ea..150f053 100644
--- a/openstack_dashboard/exceptions.py
+++ b/openstack_dashboard/exceptions.py
@@ -24,6 +24,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 swiftclient import client as swiftclient
 from troveclient import exceptions as troveclient
 
@@ -75,5 +76,6 @@ RECOVERABLE = (
     neutronclient.StateInvalidClient,
     swiftclient.ClientException,
     heatclient.HTTPException,
-    troveclient.ClientException
+    troveclient.ClientException,
+    requests.RequestException,
 )
diff --git a/openstack_dashboard/usage/views.py b/openstack_dashboard/usage/views.py
index 6d04753..fe6ff8f 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):
-- 
2.1.3

