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
This commit is contained in:
Justin Pomeroy 2014-07-10 11:28:06 -05:00
parent 96ee29c2e3
commit 407c17b38a
14 changed files with 60 additions and 21 deletions

View File

@ -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'])

View File

@ -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:

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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):