Allow horizon to function without nova

Adds conditional block to nova quotas to exclude them if nova is not
enabled; adds 'permission' checks to the project overview and
access_and_security panels to only enable them if compute is enabled;
adds permission checks on compute and image to the admin overview
and metadef panels; disables 'modify quota' and 'view usage' project
actions; disables 'update defaults' if there are no quotas available.

The 'access and security' panel still appears (under Compute) but
tabs other than the keystone endpoint and RC download tab are hidden.

Closes-Bug: #1580116
Change-Id: I1b2ddee0395ad9f55692111604b31618c4eaf69e
This commit is contained in:
Steve McLellan 2016-07-14 11:55:37 -05:00
parent d3ad1040b2
commit 018e99d20e
17 changed files with 187 additions and 69 deletions

View File

@ -27,18 +27,24 @@ from openstack_dashboard.api import nova
class NetworkClient(object):
def __init__(self, request):
neutron_enabled = base.is_service_enabled(request, 'network')
nova_enabled = base.is_service_enabled(request, 'compute')
self.secgroups, self.floating_ips = None, None
if neutron_enabled:
self.floating_ips = neutron.FloatingIpManager(request)
else:
elif nova_enabled:
self.floating_ips = nova.FloatingIpManager(request)
if (neutron_enabled and
neutron.is_extension_supported(request, 'security-group')):
self.secgroups = neutron.SecurityGroupManager(request)
else:
elif nova_enabled:
self.secgroups = nova.SecurityGroupManager(request)
@property
def enabled(self):
return self.floating_ips is not None
def floating_ip_pools_list(request):
return NetworkClient(request).floating_ips.list_pools()
@ -88,7 +94,8 @@ def floating_ip_simple_associate_supported(request):
def floating_ip_supported(request):
return NetworkClient(request).floating_ips.is_supported()
nwc = NetworkClient(request)
return nwc.enabled and nwc.floating_ips.is_supported()
def security_group_list(request):

View File

@ -16,6 +16,8 @@ from django.utils.translation import ugettext_lazy as _
from horizon import tables
from openstack_dashboard.usage import quotas
class QuotaFilterAction(tables.FilterAction):
def filter(self, table, tenants, filter_string):
@ -36,6 +38,9 @@ class UpdateDefaultQuotas(tables.LinkAction):
classes = ("ajax-modal",)
icon = "pencil"
def allowed(self, request, _=None):
return quotas.enabled_quotas(request)
def get_quota_name(quota):
QUOTA_NAMES = {

View File

@ -46,7 +46,9 @@ class ServicesViewTests(test.BaseAdminViewTests):
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
api.cinder.is_volume_service_enabled(IsA(http.HttpRequest)) \
.AndReturn(True)
.MultipleTimes().AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.MultipleTimes().AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.MultipleTimes().AndReturn(neutron_enabled)
@ -57,7 +59,7 @@ class ServicesViewTests(test.BaseAdminViewTests):
if neutron_enabled:
api.neutron.is_extension_supported(
IsA(http.HttpRequest),
'security-group').AndReturn(neutron_sg_enabled)
'security-group').MultipleTimes().AndReturn(neutron_sg_enabled)
self.mox.ReplayAll()

View File

@ -24,6 +24,7 @@ class MetadataDefinitions(horizon.Panel):
name = _("Metadata Definitions")
slug = 'metadata_defs'
policy_rules = (("image", "get_metadef_namespaces"),)
permissions = ('openstack.services.image',)
@staticmethod
def can_register():

View File

@ -27,6 +27,7 @@ class Overview(horizon.Panel):
name = _("Overview")
slug = 'overview'
policy_rules = (('identity', 'identity:list_projects'),)
permissions = ('openstack.services.compute',)
dashboard.Admin.register(Overview)

View File

@ -24,6 +24,7 @@ from keystoneclient.exceptions import Conflict # noqa
from openstack_dashboard import api
from openstack_dashboard import policy
from openstack_dashboard.usage import quotas
class RescopeTokenToProject(tables.LinkAction):
@ -103,7 +104,8 @@ class UsageLink(tables.LinkAction):
policy_rules = (("compute", "compute_extension:simple_tenant_usage:show"),)
def allowed(self, request, project):
return request.user.is_superuser
return (request.user.is_superuser and
api.base.is_service_enabled(request, 'compute'))
class CreateProject(tables.LinkAction):
@ -153,7 +155,8 @@ class ModifyQuotas(tables.LinkAction):
if api.keystone.VERSIONS.active < 3:
return True
else:
return api.keystone.is_cloud_admin(request)
return (api.keystone.is_cloud_admin(request) and
quotas.enabled_quotas(request))
def get_link_url(self, project):
step = 'update_quotas'

View File

@ -53,7 +53,8 @@ PROJECT_DETAIL_URL = reverse('horizon:identity:projects:detail', args=[1])
class TenantsViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('domain_get',
'tenant_list',
'domain_lookup')})
'domain_lookup'),
quotas: ('enabled_quotas',)})
def test_index(self):
domain = self.domains.get(id="1")
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
@ -64,6 +65,8 @@ class TenantsViewTests(test.BaseAdminViewTests):
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
quotas.enabled_quotas(IsA(http.HttpRequest)).MultipleTimes()\
.AndReturn(('instances',))
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@ -72,7 +75,8 @@ class TenantsViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_list',
'get_effective_domain_id',
'domain_lookup')})
'domain_lookup'),
quotas: ('enabled_quotas',)})
def test_index_with_domain_context(self):
domain = self.domains.get(id="1")
@ -91,6 +95,7 @@ class TenantsViewTests(test.BaseAdminViewTests):
.AndReturn([domain_tenants, False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
quotas.enabled_quotas(IsA(http.HttpRequest)).AndReturn(('instances',))
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@ -201,6 +206,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.MultipleTimes().AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.MultipleTimes().AndReturn(True)
api.cinder.is_volume_service_enabled(IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.keystone.get_default_domain(IsA(http.HttpRequest)) \
@ -1607,12 +1614,14 @@ class UsageViewTests(test.BaseAdminViewTests):
class DetailProjectViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('tenant_get',)})
@test.create_stubs({api.keystone: ('tenant_get',),
quotas: ('enabled_quotas',)})
def test_detail_view(self):
project = self.tenants.first()
api.keystone.tenant_get(IsA(http.HttpRequest), self.tenant.id) \
.AndReturn(project)
quotas.enabled_quotas(IsA(http.HttpRequest)).AndReturn(('instances',))
self.mox.ReplayAll()
res = self.client.get(PROJECT_DETAIL_URL, args=[project.id])

View File

@ -386,9 +386,12 @@ class UpdateProjectGroups(workflows.UpdateMembersStep):
class CommonQuotaWorkflow(workflows.Workflow):
def _update_project_quota(self, request, data, project_id):
disabled_quotas = quotas.get_disabled_quotas(request)
nova_data = {key: data[key] for key in
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
nova.tenant_quota_update(request, project_id, **nova_data)
# Update the project quotas.
if api.base.is_service_enabled(request, 'compute'):
nova_data = {key: data[key] for key in
set(quotas.NOVA_QUOTA_FIELDS) - disabled_quotas}
nova.tenant_quota_update(request, project_id, **nova_data)
if cinder.is_volume_service_enabled(request):
cinder_data = dict([(key, data[key]) for key in

View File

@ -374,6 +374,8 @@ class FloatingIpNeutronViewTests(FloatingIpViewTests):
.AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.MultipleTimes().AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.MultipleTimes().AndReturn(True)
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -435,6 +437,8 @@ class FloatingIpNeutronViewTests(FloatingIpViewTests):
.AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.MultipleTimes().AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.MultipleTimes().AndReturn(True)
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.nova.flavor_list(IsA(http.HttpRequest)) \

View File

@ -51,6 +51,8 @@ class LaunchImage(tables.LinkAction):
return "?".join([base_url, params])
def allowed(self, request, image=None):
if not api.base.is_service_enabled(request, 'compute'):
return False
if image and image.container_format not in NOT_LAUNCHABLE_FORMATS:
return image.status in ("active",)
return False

View File

@ -12,6 +12,7 @@
from django.conf import settings
from openstack_dashboard.api import base
from openstack_dashboard.usage import quotas
@ -49,8 +50,10 @@ def get_context(request, context=None):
_has_permission(request, (("network", "create_router"),)))
context['router_quota_exceeded'] = _quota_exceeded(request, 'routers')
context['console_type'] = getattr(settings, 'CONSOLE_TYPE', 'AUTO')
context['show_ng_launch'] = getattr(
settings, 'LAUNCH_INSTANCE_NG_ENABLED', True)
context['show_legacy_launch'] = getattr(
settings, 'LAUNCH_INSTANCE_LEGACY_ENABLED', False)
context['show_ng_launch'] = (
base.is_service_enabled(request, 'compute') and
getattr(settings, 'LAUNCH_INSTANCE_NG_ENABLED', True))
context['show_legacy_launch'] = (
base.is_service_enabled(request, 'compute') and
getattr(settings, 'LAUNCH_INSTANCE_LEGACY_ENABLED', False))
return context

View File

@ -24,3 +24,4 @@ import horizon
class Overview(horizon.Panel):
name = _("Overview")
slug = 'overview'
permissions = ('openstack.services.compute',)

View File

@ -58,6 +58,8 @@ class LaunchVolume(tables.LinkAction):
return "?".join([base_url, params])
def allowed(self, request, volume=None):
if not api.base.is_service_enabled(request, 'compute'):
return False
if getattr(volume, 'bootable', '') == 'true':
return volume.status == "available"
return False
@ -179,6 +181,9 @@ class EditAttachments(tables.LinkAction):
icon = "pencil"
def allowed(self, request, volume=None):
if not api.base.is_service_enabled(request, 'compute'):
return False
if volume:
project_id = getattr(volume, "os-vol-tenant-attr:tenant_id", None)
attach_allowed = \

View File

@ -32,6 +32,8 @@ class NetworkClientTestCase(test.APITestCase):
self.mox.StubOutWithMock(api.base, 'is_service_enabled')
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.AndReturn(True)
self.mox.ReplayAll()
nc = api.network.NetworkClient(self.request)
@ -42,6 +44,8 @@ class NetworkClientTestCase(test.APITestCase):
self.mox.StubOutWithMock(api.base, 'is_service_enabled')
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.AndReturn(True)
self.neutronclient = self.stub_neutronclient()
self.neutronclient.list_extensions() \
.AndReturn({'extensions': self.api_extensions.list()})
@ -55,6 +59,8 @@ class NetworkClientTestCase(test.APITestCase):
self.mox.StubOutWithMock(api.base, 'is_service_enabled')
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.AndReturn(True)
self.neutronclient = self.stub_neutronclient()
self.neutronclient.list_extensions().AndReturn({'extensions': []})
self.mox.ReplayAll()
@ -70,6 +76,8 @@ class NetworkApiNovaTestBase(test.APITestCase):
self.mox.StubOutWithMock(api.base, 'is_service_enabled')
api.base.is_service_enabled(IsA(http.HttpRequest), 'network') \
.AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.AndReturn(True)
class NetworkApiNovaSecurityGroupTests(NetworkApiNovaTestBase):
@ -339,6 +347,8 @@ class NetworkApiNeutronSecurityGroupTests(NetworkApiNeutronTestBase):
super(NetworkApiNeutronSecurityGroupTests, self).setUp()
self.qclient.list_extensions() \
.AndReturn({'extensions': self.api_extensions.list()})
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.AndReturn(True)
self.sg_dict = dict([(sg['id'], sg['name']) for sg
in self.api_q_secgroups.list()])
@ -521,6 +531,8 @@ class NetworkApiNeutronFloatingIpTests(NetworkApiNeutronTestBase):
super(NetworkApiNeutronFloatingIpTests, self).setUp()
self.qclient.list_extensions() \
.AndReturn({'extensions': self.api_extensions.list()})
api.base.is_service_enabled(IsA(http.HttpRequest), 'compute') \
.AndReturn(True)
@override_settings(OPENSTACK_NEUTRON_NETWORK={'enable_router': True})
def test_floating_ip_supported(self):

View File

@ -33,26 +33,36 @@ from openstack_dashboard.usage import quotas
class QuotaTests(test.APITestCase):
def get_usages(self, with_volume=True, nova_quotas_enabled=True):
if nova_quotas_enabled:
usages = {'injected_file_content_bytes': {'quota': 1},
'metadata_items': {'quota': 1},
'injected_files': {'quota': 1},
'security_groups': {'quota': 10},
'security_group_rules': {'quota': 20},
'fixed_ips': {'quota': 10},
'ram': {'available': 8976, 'used': 1024, 'quota': 10000},
'floating_ips': {'available': 0, 'used': 2, 'quota': 1},
'instances': {'available': 8, 'used': 2, 'quota': 10},
'cores': {'available': 8, 'used': 2, 'quota': 10}}
else:
inf = float('inf')
usages = {'security_groups': {'available': inf, 'quota': inf},
'ram': {'available': inf, 'used': 1024, 'quota': inf},
'floating_ips': {
'available': inf, 'used': 2, 'quota': inf},
'instances': {'available': inf, 'used': 2, 'quota': inf},
'cores': {'available': inf, 'used': 2, 'quota': inf}}
def get_usages(self, with_volume=True, with_compute=True,
nova_quotas_enabled=True):
usages = {}
if with_compute:
# These are all nova fields; the neutron ones are named slightly
# differently and aren't included in here yet
if nova_quotas_enabled:
usages.update({
'injected_file_content_bytes': {'quota': 1},
'metadata_items': {'quota': 1},
'injected_files': {'quota': 1},
'security_groups': {'quota': 10},
'security_group_rules': {'quota': 20},
'fixed_ips': {'quota': 10},
'ram': {'available': 8976, 'used': 1024, 'quota': 10000},
'floating_ips': {'available': 0, 'used': 2, 'quota': 1},
'instances': {'available': 8, 'used': 2, 'quota': 10},
'cores': {'available': 8, 'used': 2, 'quota': 10}
})
else:
inf = float('inf')
usages.update({
'security_groups': {'available': inf, 'quota': inf},
'ram': {'available': inf, 'used': 1024, 'quota': inf},
'floating_ips': {'available': inf, 'used': 2,
'quota': inf},
'instances': {'available': inf, 'used': 2, 'quota': inf},
'cores': {'available': inf, 'used': 2, 'quota': inf}
})
if with_volume:
usages.update({'volumes': {'available': 0, 'used': 4, 'quota': 1},
'snapshots': {'available': 0, 'used': 3,
@ -78,26 +88,34 @@ class QuotaTests(test.APITestCase):
'tenant_quota_get',
'is_volume_service_enabled')})
def _test_tenant_quota_usages(self, nova_quotas_enabled=True,
with_volume=True):
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
with_compute=True, with_volume=True):
cinder.is_volume_service_enabled(IsA(http.HttpRequest)).AndReturn(
with_volume)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.network.floating_ip_supported(IsA(http.HttpRequest)) \
.AndReturn(True)
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
search_opts = {'tenant_id': self.request.user.tenant_id}
api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts,
all_tenants=True) \
.AndReturn([servers, False])
api.base.is_service_enabled(
IsA(http.HttpRequest), 'compute'
).MultipleTimes().AndReturn(with_compute)
if with_compute:
servers = [s for s in self.servers.list()
if s.tenant_id == self.request.user.tenant_id]
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.network.floating_ip_supported(IsA(http.HttpRequest)) \
.AndReturn(True)
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
search_opts = {'tenant_id': self.request.user.tenant_id}
api.nova.server_list(IsA(http.HttpRequest),
search_opts=search_opts,
all_tenants=True) \
.AndReturn([servers, False])
if nova_quotas_enabled:
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
if with_volume:
opts = {'all_tenants': 1,
'project_id': self.request.user.tenant_id}
@ -112,7 +130,8 @@ class QuotaTests(test.APITestCase):
quota_usages = quotas.tenant_quota_usages(self.request)
expected_output = self.get_usages(
nova_quotas_enabled=nova_quotas_enabled, with_volume=with_volume)
nova_quotas_enabled=nova_quotas_enabled, with_volume=with_volume,
with_compute=with_compute)
# Compare internal structure of usages to expected.
self.assertItemsEqual(expected_output, quota_usages.usages)
@ -125,6 +144,7 @@ class QuotaTests(test.APITestCase):
@override_settings(OPENSTACK_HYPERVISOR_FEATURES={'enable_quotas': False})
def test_tenant_quota_usages_wo_nova_quotas(self):
self._test_tenant_quota_usages(nova_quotas_enabled=False,
with_compute=True,
with_volume=False)
@override_settings(OPENSTACK_HYPERVISOR_FEATURES={'enable_quotas': False})
@ -135,11 +155,15 @@ class QuotaTests(test.APITestCase):
False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
# Nova enabled but quotas disabled
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').AndReturn(True)
self.mox.ReplayAll()
result_quotas = quotas.get_disabled_quotas(self.request)
expected_quotas = list(quotas.CINDER_QUOTA_FIELDS) + \
list(quotas.NEUTRON_QUOTA_FIELDS) + list(quotas.NOVA_QUOTA_FIELDS)
list(quotas.NEUTRON_QUOTA_FIELDS) + \
list(quotas.NOVA_QUOTA_FIELDS) + list(quotas.MISSING_QUOTA_FIELDS)
self.assertItemsEqual(result_quotas, expected_quotas)
@test.create_stubs({api.nova: ('server_list',
@ -158,6 +182,8 @@ class QuotaTests(test.APITestCase):
).AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
@ -197,6 +223,8 @@ class QuotaTests(test.APITestCase):
).AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
@ -244,6 +272,8 @@ class QuotaTests(test.APITestCase):
).AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
@ -293,6 +323,8 @@ class QuotaTests(test.APITestCase):
).AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
@ -332,8 +364,7 @@ class QuotaTests(test.APITestCase):
quotas._get_tenant_volume_usages(self.request, {}, [], None)
@test.create_stubs({api.nova: ('tenant_quota_get',),
api.base: ('is_service_enabled',),
@test.create_stubs({api.base: ('is_service_enabled',),
api.cinder: ('tenant_quota_get',
'is_volume_service_enabled'),
exceptions: ('handle',)})
@ -343,8 +374,8 @@ class QuotaTests(test.APITestCase):
).AndReturn(True)
api.base.is_service_enabled(IsA(http.HttpRequest),
'network').AndReturn(False)
api.nova.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndReturn(self.quotas.first())
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').AndReturn(False)
api.cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
.AndRaise(cinder.cinder_exception.ClientException('test'))
exceptions.handle(IsA(http.HttpRequest),
@ -353,17 +384,16 @@ class QuotaTests(test.APITestCase):
quotas._get_quota_data(self.request, 'tenant_quota_get')
@test.create_stubs({api.nova: ('tenant_absolute_limits',),
api.base: ('is_service_enabled',),
@test.create_stubs({api.base: ('is_service_enabled',),
api.cinder: ('tenant_absolute_limits',
'is_volume_service_enabled'),
exceptions: ('handle',)})
def test_tenant_limit_usages_cinder_exception(self):
api.base.is_service_enabled(IsA(http.HttpRequest),
'compute').AndReturn(False)
api.cinder.is_volume_service_enabled(
IsA(http.HttpRequest)
).AndReturn(True)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest),
reserved=True).AndReturn({})
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndRaise(cinder.cinder_exception.ClientException('test'))
exceptions.handle(IsA(http.HttpRequest),

View File

@ -141,10 +141,14 @@ def _get_quota_data(request, method_name, disabled_quotas=None,
quotasets = []
if not tenant_id:
tenant_id = request.user.tenant_id
quotasets.append(getattr(nova, method_name)(request, tenant_id))
qs = base.QuotaSet()
if disabled_quotas is None:
disabled_quotas = get_disabled_quotas(request)
qs = base.QuotaSet()
if 'instances' not in disabled_quotas:
quotasets.append(getattr(nova, method_name)(request, tenant_id))
if 'volumes' not in disabled_quotas:
try:
quotasets.append(getattr(cinder, method_name)(request, tenant_id))
@ -231,10 +235,6 @@ def get_tenant_quota_data(request, disabled_quotas=None, tenant_id=None):
def get_disabled_quotas(request):
disabled_quotas = set([])
# Nova
if not nova.can_set_quotas():
disabled_quotas.update(NOVA_QUOTA_FIELDS)
# Cinder
if not cinder.is_volume_service_enabled(request):
disabled_quotas.update(CINDER_QUOTA_FIELDS)
@ -260,10 +260,25 @@ def get_disabled_quotas(request):
LOG.exception("There was an error checking if the Neutron "
"quotas extension is enabled.")
# Nova
if not (base.is_service_enabled(request, 'compute') and
nova.can_set_quotas()):
disabled_quotas.update(NOVA_QUOTA_FIELDS)
# The 'missing' quota fields are all nova (this is hardcoded in
# dashboards.admin.defaults.workflows)
disabled_quotas.update(MISSING_QUOTA_FIELDS)
# There appear to be no glance quota fields currently
return disabled_quotas
def _get_tenant_compute_usages(request, usages, disabled_quotas, tenant_id):
# Unlike the other services it can be the case that nova is enabled but
# doesn't support quotas, in which case we still want to get usage info,
# so don't rely on '"instances" in disabled_quotas' as elsewhere
if not base.is_service_enabled(request, 'compute'):
return
if tenant_id:
# determine if the user has permission to view across projects
# there are cases where an administrator wants to check the quotas
@ -396,7 +411,8 @@ def tenant_limit_usages(request):
limits = {}
try:
limits.update(nova.tenant_absolute_limits(request, reserved=True))
if base.is_service_enabled(request, 'compute'):
limits.update(nova.tenant_absolute_limits(request, reserved=True))
except Exception:
msg = _("Unable to retrieve compute limit information.")
exceptions.handle(request, msg)
@ -419,3 +435,8 @@ def tenant_limit_usages(request):
exceptions.handle(request, msg)
return limits
def enabled_quotas(request):
"""Returns the list of quotas available minus those that are disabled"""
return set(QUOTA_FIELDS) - get_disabled_quotas(request)

View File

@ -0,0 +1,9 @@
---
prelude: >
Horizon no longer requires Nova (or Glance) to function;
it will run as long as keystone is present (for instance,
swift-only deployments).
features:
Nova and Glance are no longer required in order to run
Horizon. As long as keystone is present, Horizon will
run correctly.