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:
parent
96ee29c2e3
commit
407c17b38a
@ -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'])
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user