Full support for dashboard and panel configuration via service catalog.
There are no longer any dependencies on settings for whether or not particular components are made available in the site. Implements blueprint toggle-features. Also fixes bug 929983, making the Horizon object a proper singleton and ensuring test isolation for the base horizon tests. Fixes a case where a missing service catalog would cause a 500 error. Fixes bug 930833, Change-Id: If19762afe75859e63aa7bd5128a6795655df2c90
This commit is contained in:
parent
797c497312
commit
aed4766cc9
@ -36,5 +36,8 @@ from horizon.api.glance import *
|
||||
from horizon.api.keystone import *
|
||||
from horizon.api.nova import *
|
||||
from horizon.api.swift import *
|
||||
if settings.QUANTUM_ENABLED:
|
||||
# Quantum is optional. Ignore it if it's not installed.
|
||||
try:
|
||||
from horizon.api.quantum import *
|
||||
except ImportError:
|
||||
pass
|
||||
|
@ -36,7 +36,8 @@ from django.utils.importlib import import_module
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from horizon.decorators import require_roles, _current_component
|
||||
from horizon.decorators import (require_roles, require_services,
|
||||
_current_component)
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -108,7 +109,7 @@ class Registry(object):
|
||||
raise ValueError('Only classes may be registered.')
|
||||
elif not issubclass(cls, self._registerable_class):
|
||||
raise ValueError('Only %s classes or subclasses may be registered.'
|
||||
% self._registerable_class)
|
||||
% self._registerable_class.__name__)
|
||||
|
||||
if cls not in self._registry:
|
||||
cls._registered_with = self
|
||||
@ -135,9 +136,9 @@ class Registry(object):
|
||||
|
||||
def _registered(self, cls):
|
||||
if inspect.isclass(cls) and issubclass(cls, self._registerable_class):
|
||||
cls = self._registry.get(cls, None)
|
||||
if cls:
|
||||
return cls
|
||||
found = self._registry.get(cls, None)
|
||||
if found:
|
||||
return found
|
||||
else:
|
||||
# Allow for fetching by slugs as well.
|
||||
for registered in self._registry.values():
|
||||
@ -153,9 +154,10 @@ class Registry(object):
|
||||
"parent": parent,
|
||||
"name": self.name})
|
||||
else:
|
||||
slug = getattr(cls, "slug", cls)
|
||||
raise NotRegistered('%(type)s with slug "%(slug)s" is not '
|
||||
'registered.'
|
||||
% {"type": class_name, "slug": cls})
|
||||
'registered.' % {"type": class_name,
|
||||
"slug": slug})
|
||||
|
||||
|
||||
class Panel(HorizonComponent):
|
||||
@ -183,6 +185,11 @@ class Panel(HorizonComponent):
|
||||
is combined cumulatively with any roles required on the
|
||||
``Dashboard`` class with which it is registered.
|
||||
|
||||
.. attribute:: services
|
||||
|
||||
A list of service names, all of which must be in the service catalog
|
||||
in order for this panel to be available.
|
||||
|
||||
.. attribute:: urls
|
||||
|
||||
Path to a URLconf of views for this panel using dotted Python
|
||||
@ -235,7 +242,9 @@ class Panel(HorizonComponent):
|
||||
|
||||
# Apply access controls to all views in the patterns
|
||||
roles = getattr(self, 'roles', [])
|
||||
services = getattr(self, 'services', [])
|
||||
_decorate_urlconf(urlpatterns, require_roles, roles)
|
||||
_decorate_urlconf(urlpatterns, require_services, services)
|
||||
_decorate_urlconf(urlpatterns, _current_component, panel=self)
|
||||
|
||||
# Return the three arguments to django.conf.urls.defaults.include
|
||||
@ -295,13 +304,18 @@ class Dashboard(Registry, HorizonComponent):
|
||||
for this dashboard, that's the panel that is displayed.
|
||||
Default: ``None``.
|
||||
|
||||
.. attribute: roles
|
||||
.. attribute:: roles
|
||||
|
||||
A list of role names, all of which a user must possess in order
|
||||
to access any panel registered with this dashboard. This attribute
|
||||
is combined cumulatively with any roles required on individual
|
||||
:class:`~horizon.Panel` classes.
|
||||
|
||||
.. attribute:: services
|
||||
|
||||
A list of service names, all of which must be in the service catalog
|
||||
in order for this dashboard to be available.
|
||||
|
||||
.. attribute:: urls
|
||||
|
||||
Optional path to a URLconf of additional views for this dashboard
|
||||
@ -410,7 +424,9 @@ class Dashboard(Registry, HorizonComponent):
|
||||
_decorate_urlconf(urlpatterns, login_required)
|
||||
# Apply access controls to all views in the patterns
|
||||
roles = getattr(self, 'roles', [])
|
||||
services = getattr(self, 'services', [])
|
||||
_decorate_urlconf(urlpatterns, require_roles, roles)
|
||||
_decorate_urlconf(urlpatterns, require_services, services)
|
||||
_decorate_urlconf(urlpatterns, _current_component, dashboard=self)
|
||||
|
||||
# Return the three arguments to django.conf.urls.defaults.include
|
||||
@ -437,13 +453,11 @@ class Dashboard(Registry, HorizonComponent):
|
||||
@classmethod
|
||||
def register(cls, panel):
|
||||
""" Registers a :class:`~horizon.Panel` with this dashboard. """
|
||||
from horizon import Horizon
|
||||
return Horizon.register_panel(cls, panel)
|
||||
|
||||
@classmethod
|
||||
def unregister(cls, panel):
|
||||
""" Unregisters a :class:`~horizon.Panel` from this dashboard. """
|
||||
from horizon import Horizon
|
||||
return Horizon.unregister_panel(cls, panel)
|
||||
|
||||
|
||||
@ -465,7 +479,8 @@ class LazyURLPattern(SimpleLazyObject):
|
||||
|
||||
|
||||
class Site(Registry, HorizonComponent):
|
||||
""" The core OpenStack Dashboard class. """
|
||||
""" The overarching class which encompasses all dashboards and panels. """
|
||||
|
||||
# Required for registry
|
||||
_registerable_class = Dashboard
|
||||
|
||||
@ -620,9 +635,7 @@ class Site(Registry, HorizonComponent):
|
||||
def _urls(self):
|
||||
""" Constructs the URLconf for Horizon from registered Dashboards. """
|
||||
urlpatterns = self._get_default_urlpatterns()
|
||||
|
||||
self._autodiscover()
|
||||
|
||||
# Add in each dashboard's views.
|
||||
for dash in self._registry.values():
|
||||
urlpatterns += patterns('',
|
||||
@ -653,5 +666,19 @@ class Site(Registry, HorizonComponent):
|
||||
if module_has_submodule(mod, mod_name):
|
||||
raise
|
||||
|
||||
|
||||
class HorizonSite(Site):
|
||||
"""
|
||||
A singleton implementation of Site such that all dealings with horizon
|
||||
get the same instance no matter what. There can be only one.
|
||||
"""
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not cls._instance:
|
||||
cls._instance = super(Site, cls).__new__(cls, *args, **kwargs)
|
||||
return cls._instance
|
||||
|
||||
|
||||
# The one true Horizon
|
||||
Horizon = Site()
|
||||
Horizon = HorizonSite()
|
||||
|
@ -34,17 +34,15 @@ LOG = logging.getLogger(__name__)
|
||||
def horizon(request):
|
||||
""" The main Horizon context processor. Required for Horizon to function.
|
||||
|
||||
Adds three variables to the request context:
|
||||
The following variables are added to the request context:
|
||||
|
||||
``authorized_tenants``
|
||||
A list of tenant objects which the current user has access to.
|
||||
|
||||
``object_store_configured``
|
||||
Boolean. Will be ``True`` if there is a service of type
|
||||
``object-store`` in the user's ``ServiceCatalog``.
|
||||
``regions``
|
||||
|
||||
``network_configured``
|
||||
Boolean. Will be ``True`` if ``settings.QUANTUM_ENABLED`` is ``True``.
|
||||
A dictionary containing information about region support, the current
|
||||
region, and available regions.
|
||||
|
||||
Additionally, it sets the names ``True`` and ``False`` in the context
|
||||
to their boolean equivalents for convenience.
|
||||
@ -63,17 +61,6 @@ def horizon(request):
|
||||
if request.user.is_authenticated():
|
||||
context['authorized_tenants'] = request.user.authorized_tenants
|
||||
|
||||
# Object Store/Swift context
|
||||
catalog = getattr(request.user, 'service_catalog', [])
|
||||
object_store = catalog and api.get_service_from_catalog(catalog,
|
||||
'object-store')
|
||||
context['object_store_configured'] = object_store
|
||||
|
||||
# Quantum context
|
||||
# TODO(gabriel): Convert to service catalog check when Quantum starts
|
||||
# supporting keystone integration.
|
||||
context['network_configured'] = getattr(settings, 'QUANTUM_ENABLED', None)
|
||||
|
||||
# Region context/support
|
||||
available_regions = getattr(settings, 'AVAILABLE_REGIONS', [])
|
||||
regions = {'support': len(available_regions) > 1,
|
||||
|
@ -27,9 +27,6 @@ from horizon.dashboards.nova import dashboard
|
||||
class Containers(horizon.Panel):
|
||||
name = _("Containers")
|
||||
slug = 'containers'
|
||||
|
||||
def nav(self, context):
|
||||
return context['object_store_configured']
|
||||
|
||||
services = ('object-store',)
|
||||
|
||||
dashboard.Nova.register(Containers)
|
||||
|
@ -25,9 +25,7 @@ from horizon.dashboards.nova import dashboard
|
||||
class Networks(horizon.Panel):
|
||||
name = "Networks"
|
||||
slug = 'networks'
|
||||
|
||||
def nav(self, context):
|
||||
return context.get('network_configured', False)
|
||||
services = ("network",)
|
||||
|
||||
|
||||
dashboard.Nova.register(Networks)
|
||||
|
@ -25,7 +25,7 @@ import functools
|
||||
|
||||
from django.utils.decorators import available_attrs
|
||||
|
||||
from horizon.exceptions import NotAuthorized
|
||||
from horizon.exceptions import NotAuthorized, NotFound
|
||||
|
||||
|
||||
def _current_component(view_func, dashboard=None, panel=None):
|
||||
@ -79,6 +79,46 @@ def require_roles(view_func, required):
|
||||
return view_func
|
||||
|
||||
|
||||
def require_services(view_func, required):
|
||||
""" Enforces service-based access controls.
|
||||
|
||||
:param list required: A tuple of service type names, all of which the
|
||||
must be present in the service catalog in order
|
||||
access the decorated view.
|
||||
|
||||
Example usage::
|
||||
|
||||
from horizon.decorators import require_services
|
||||
|
||||
|
||||
@require_services(['object-store'])
|
||||
def my_swift_view(request):
|
||||
...
|
||||
|
||||
Raises a :exc:`~horizon.exceptions.NotFound` exception if the
|
||||
requirements are not met.
|
||||
"""
|
||||
# We only need to check each service once for a view, so we'll use a set
|
||||
current_services = getattr(view_func, '_required_services', set([]))
|
||||
view_func._required_services = current_services | set(required)
|
||||
|
||||
@functools.wraps(view_func, assigned=available_attrs(view_func))
|
||||
def dec(request, *args, **kwargs):
|
||||
if request.user.is_authenticated():
|
||||
services = set([service['type'] for service in
|
||||
request.user.service_catalog])
|
||||
# set operator <= tests that all members of set 1 are in set 2
|
||||
if view_func._required_services <= set(services):
|
||||
return view_func(request, *args, **kwargs)
|
||||
raise NotFound("The services for this view are not available.")
|
||||
|
||||
# If we don't have any services, just return the original view.
|
||||
if required:
|
||||
return dec
|
||||
else:
|
||||
return view_func
|
||||
|
||||
|
||||
def enforce_admin_access(view_func):
|
||||
""" Marks a view as requiring the ``"admin"`` role for access. """
|
||||
return require_roles(view_func, ('admin',))
|
||||
|
@ -23,6 +23,7 @@ Middleware provided and used by Horizon.
|
||||
|
||||
import logging
|
||||
|
||||
from django import http
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -56,7 +57,7 @@ class HorizonMiddleware(object):
|
||||
authd = api.tenant_list_for_token(request,
|
||||
token,
|
||||
endpoint_type='internalURL')
|
||||
except Exception, e:
|
||||
except:
|
||||
authd = []
|
||||
LOG.exception('Could not retrieve tenant list.')
|
||||
if hasattr(request.user, 'message_set'):
|
||||
@ -65,11 +66,18 @@ class HorizonMiddleware(object):
|
||||
request.user.authorized_tenants = authd
|
||||
|
||||
def process_exception(self, request, exception):
|
||||
""" Catch NotAuthorized and Http302 and handle them gracefully. """
|
||||
"""
|
||||
Catches internal Horizon exception classes such as NotAuthorized,
|
||||
NotFound and Http302 and handles them gracefully.
|
||||
"""
|
||||
if isinstance(exception, exceptions.NotAuthorized):
|
||||
messages.error(request, unicode(exception))
|
||||
return shortcuts.redirect('/auth/login')
|
||||
|
||||
# If an internal "NotFound" error gets this far, return a real 404.
|
||||
if isinstance(exception, exceptions.NotFound):
|
||||
raise http.Http404(exception)
|
||||
|
||||
if isinstance(exception, exceptions.Http302):
|
||||
if exception.message:
|
||||
messages.error(request, exception.message)
|
||||
|
@ -1,14 +1,16 @@
|
||||
{% load horizon %}
|
||||
|
||||
{% for heading, panels in components.iteritems %}
|
||||
<h4>{{ heading }}</h4>
|
||||
<ul class="main_nav">
|
||||
{% for panel in panels %}
|
||||
{% if user|can_haz:panel %}
|
||||
<li>
|
||||
{% with panels|can_haz_list:user as filtered_panels %}
|
||||
{% if filtered_panels %}
|
||||
<h4>{{ heading }}</h4>
|
||||
<ul class="main_nav">
|
||||
{% for panel in filtered_panels %}
|
||||
<li>
|
||||
<a href="{{ panel.get_absolute_url }}" {% if current == panel.slug %}class="active"{% endif %} tabindex='1'>{{ panel.name }}</a>
|
||||
</li>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
|
@ -28,16 +28,32 @@ register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def can_haz(user, component):
|
||||
""" Checks if the given user has the necessary roles for the component. """
|
||||
"""
|
||||
Checks if the given user meets the requirements for the component. This
|
||||
includes both user roles and services in the service catalog.
|
||||
"""
|
||||
if hasattr(user, 'roles'):
|
||||
user_roles = set([role['name'].lower() for role in user.roles])
|
||||
else:
|
||||
user_roles = set([])
|
||||
if set(getattr(component, 'roles', [])) <= user_roles:
|
||||
roles_statisfied = set(getattr(component, 'roles', [])) <= user_roles
|
||||
|
||||
if hasattr(user, 'roles'):
|
||||
services = set([service['type'] for service in user.service_catalog])
|
||||
else:
|
||||
services = set([])
|
||||
services_statisfied = set(getattr(component, 'services', [])) <= services
|
||||
|
||||
if roles_statisfied and services_statisfied:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@register.filter
|
||||
def can_haz_list(components, user):
|
||||
return [component for component in components if can_haz(user, component)]
|
||||
|
||||
|
||||
@register.inclusion_tag('horizon/_nav_list.html', takes_context=True)
|
||||
def horizon_main_nav(context):
|
||||
""" Generates top-level dashboard navigation entries. """
|
||||
|
@ -61,9 +61,7 @@ class TestCase(django_test.TestCase):
|
||||
TEST_CONTEXT = {'authorized_tenants': [{'enabled': True,
|
||||
'name': 'aTenant',
|
||||
'id': '1',
|
||||
'description': "None"}],
|
||||
'object_store_configured': False,
|
||||
'network_configured': False}
|
||||
'description': "None"}]}
|
||||
|
||||
TEST_SERVICE_CATALOG = [
|
||||
{"endpoints": [{
|
||||
@ -94,6 +92,13 @@ class TestCase(django_test.TestCase):
|
||||
"publicURL": "http://cdn.admin-nets.local:5000/v2.0"}],
|
||||
"type": "identity",
|
||||
"name": "identity"},
|
||||
{"endpoints": [{
|
||||
"adminURL": "http://example.com:9696/quantum",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://example.com:9696/quantum",
|
||||
"publicURL": "http://example.com:9696/quantum"}],
|
||||
"type": "network",
|
||||
"name": "quantum"},
|
||||
{"endpoints": [{
|
||||
"adminURL": "http://swift/swiftapi/admin",
|
||||
"region": "RegionOne",
|
||||
|
@ -18,38 +18,74 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from django.conf import settings
|
||||
from django.core import urlresolvers
|
||||
from django.test.client import Client
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
import horizon
|
||||
from horizon import base
|
||||
from horizon import exceptions
|
||||
from horizon import test
|
||||
from horizon import users
|
||||
from horizon.base import Horizon
|
||||
|
||||
|
||||
class MyDash(horizon.Dashboard):
|
||||
name = "My Dashboard"
|
||||
slug = "mydash"
|
||||
default_panel = "myslug"
|
||||
|
||||
|
||||
class MyPanel(horizon.Panel):
|
||||
name = "My Panel"
|
||||
slug = "myslug"
|
||||
services = ("compute",)
|
||||
urls = 'horizon.tests.test_panel_urls'
|
||||
|
||||
|
||||
class HorizonTests(test.TestCase):
|
||||
class BaseHorizonTests(test.TestCase):
|
||||
def setUp(self):
|
||||
super(HorizonTests, self).setUp()
|
||||
self._orig_horizon = copy.deepcopy(base.Horizon)
|
||||
super(BaseHorizonTests, self).setUp()
|
||||
# Trigger discovery, registration, and URLconf generation if it
|
||||
# hasn't happened yet.
|
||||
base.Horizon._urls()
|
||||
# Store our original dashboards
|
||||
self._discovered_dashboards = base.Horizon._registry.keys()
|
||||
# Gather up and store our original panels for each dashboard
|
||||
self._discovered_panels = {}
|
||||
for dash in self._discovered_dashboards:
|
||||
panels = base.Horizon._registry[dash]._registry.keys()
|
||||
self._discovered_panels[dash] = panels
|
||||
|
||||
def tearDown(self):
|
||||
super(HorizonTests, self).tearDown()
|
||||
base.Horizon = self._orig_horizon
|
||||
super(BaseHorizonTests, self).tearDown()
|
||||
# Destroy our singleton and re-create it.
|
||||
base.HorizonSite._instance = None
|
||||
del base.Horizon
|
||||
base.Horizon = base.HorizonSite()
|
||||
# Reload the convenience references to Horizon stored in __init__
|
||||
reload(import_module("horizon"))
|
||||
# Re-register our original dashboards and panels.
|
||||
# This is necessary because autodiscovery only works on the first
|
||||
# import, and calling reload introduces innumerable additional
|
||||
# problems. Manual re-registration is the only good way for testing.
|
||||
for dash in self._discovered_dashboards:
|
||||
base.Horizon.register(dash)
|
||||
for panel in self._discovered_panels[dash]:
|
||||
dash.register(panel)
|
||||
|
||||
def _reload_urls(self):
|
||||
'''
|
||||
Clears out the URL caches, reloads the root urls module, and
|
||||
re-triggers the autodiscovery mechanism for Horizon. Allows URLs
|
||||
to be re-calculated after registering new dashboards. Useful
|
||||
only for testing and should never be used on a live site.
|
||||
'''
|
||||
urlresolvers.clear_url_caches()
|
||||
reload(import_module(settings.ROOT_URLCONF))
|
||||
base.Horizon._urls()
|
||||
|
||||
|
||||
class HorizonTests(BaseHorizonTests):
|
||||
def test_registry(self):
|
||||
""" Verify registration and autodiscovery work correctly.
|
||||
|
||||
@ -57,11 +93,10 @@ class HorizonTests(test.TestCase):
|
||||
by virtue of the fact that the dashboards listed in
|
||||
``settings.INSTALLED_APPS`` are loaded from the start.
|
||||
"""
|
||||
|
||||
# Registration
|
||||
self.assertEqual(len(Horizon._registry), 3)
|
||||
self.assertEqual(len(base.Horizon._registry), 3)
|
||||
horizon.register(MyDash)
|
||||
self.assertEqual(len(Horizon._registry), 4)
|
||||
self.assertEqual(len(base.Horizon._registry), 4)
|
||||
with self.assertRaises(ValueError):
|
||||
horizon.register(MyPanel)
|
||||
with self.assertRaises(ValueError):
|
||||
@ -81,23 +116,24 @@ class HorizonTests(test.TestCase):
|
||||
'<Dashboard: My Dashboard>'])
|
||||
|
||||
# Removal
|
||||
self.assertEqual(len(Horizon._registry), 4)
|
||||
self.assertEqual(len(base.Horizon._registry), 4)
|
||||
horizon.unregister(MyDash)
|
||||
self.assertEqual(len(Horizon._registry), 3)
|
||||
self.assertEqual(len(base.Horizon._registry), 3)
|
||||
with self.assertRaises(base.NotRegistered):
|
||||
horizon.get_dashboard(MyDash)
|
||||
|
||||
def test_site(self):
|
||||
self.assertEqual(unicode(Horizon), "Horizon")
|
||||
self.assertEqual(repr(Horizon), "<Site: Horizon>")
|
||||
dash = Horizon.get_dashboard('nova')
|
||||
self.assertEqual(Horizon.get_default_dashboard(), dash)
|
||||
self.assertEqual(unicode(base.Horizon), "Horizon")
|
||||
self.assertEqual(repr(base.Horizon), "<Site: Horizon>")
|
||||
dash = base.Horizon.get_dashboard('nova')
|
||||
self.assertEqual(base.Horizon.get_default_dashboard(), dash)
|
||||
user = users.User()
|
||||
self.assertEqual(Horizon.get_user_home(user), dash.get_absolute_url())
|
||||
self.assertEqual(base.Horizon.get_user_home(user),
|
||||
dash.get_absolute_url())
|
||||
|
||||
def test_dashboard(self):
|
||||
syspanel = horizon.get_dashboard("syspanel")
|
||||
self.assertEqual(syspanel._registered_with, Horizon)
|
||||
self.assertEqual(syspanel._registered_with, base.Horizon)
|
||||
self.assertQuerysetEqual(syspanel.get_panels()['System Panel'],
|
||||
['<Panel: Overview>',
|
||||
'<Panel: Instances>',
|
||||
@ -133,7 +169,7 @@ class HorizonTests(test.TestCase):
|
||||
syspanel = horizon.get_dashboard("syspanel")
|
||||
instances = syspanel.get_panel("instances")
|
||||
instances.index_url_name = "does_not_exist"
|
||||
with self.assertRaises(NoReverseMatch):
|
||||
with self.assertRaises(urlresolvers.NoReverseMatch):
|
||||
instances.get_absolute_url()
|
||||
instances.index_url_name = "index"
|
||||
self.assertEqual(instances.get_absolute_url(), "/syspanel/instances/")
|
||||
@ -145,18 +181,50 @@ class HorizonTests(test.TestCase):
|
||||
iter(urlpatterns)
|
||||
reversed(urlpatterns)
|
||||
|
||||
def test_horizon_test_isolation_1(self):
|
||||
""" Isolation Test Part 1: sets a value. """
|
||||
syspanel = horizon.get_dashboard("syspanel")
|
||||
syspanel.evil = True
|
||||
|
||||
class HorizonBaseViewTests(test.BaseViewTests):
|
||||
def setUp(self):
|
||||
super(HorizonBaseViewTests, self).setUp()
|
||||
users.get_user_from_request = self._real_get_user_from_request
|
||||
def test_horizon_test_isolation_2(self):
|
||||
""" Isolation Test Part 2: The value set in part 1 should be gone. """
|
||||
syspanel = horizon.get_dashboard("syspanel")
|
||||
self.assertFalse(hasattr(syspanel, "evil"))
|
||||
|
||||
|
||||
class HorizonBaseViewTests(BaseHorizonTests, test.BaseViewTests):
|
||||
def test_public(self):
|
||||
users.get_user_from_request = self._real_get_user_from_request
|
||||
settings = horizon.get_dashboard("settings")
|
||||
# Known to have no restrictions on it other than being logged in.
|
||||
user_panel = settings.get_panel("user")
|
||||
url = user_panel.get_absolute_url()
|
||||
client = Client() # Get a clean, logged out client instance.
|
||||
# Get a clean, logged out client instance.
|
||||
client = Client()
|
||||
client.logout()
|
||||
resp = client.get(url)
|
||||
self.assertRedirectsNoFollow(resp, '/accounts/login/?next=/settings/')
|
||||
|
||||
def test_required_services(self):
|
||||
horizon.register(MyDash)
|
||||
MyDash.register(MyPanel)
|
||||
dash = horizon.get_dashboard("mydash")
|
||||
panel = dash.get_panel('myslug')
|
||||
self._reload_urls()
|
||||
|
||||
# With the required service, the page returns fine.
|
||||
resp = self.client.get(panel.get_absolute_url())
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# Remove the required service from the service catalog and we
|
||||
# should get a 404.
|
||||
new_catalog = [service for service in self.request.user.service_catalog
|
||||
if service['type'] != MyPanel.services[0]]
|
||||
tenants = self.TEST_CONTEXT['authorized_tenants']
|
||||
self.setActiveUser(token=self.TEST_TOKEN,
|
||||
username=self.TEST_USER,
|
||||
tenant_id=self.TEST_TENANT,
|
||||
service_catalog=new_catalog,
|
||||
authorized_tenants=tenants)
|
||||
resp = self.client.get(panel.get_absolute_url())
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
@ -52,15 +52,3 @@ class ContextProcessorTests(test.TestCase):
|
||||
self.assertEqual(len(context['authorized_tenants']), 1)
|
||||
tenant = context['authorized_tenants'].pop()
|
||||
self.assertEqual(tenant['id'], self.TEST_TENANT)
|
||||
|
||||
def test_object_store(self):
|
||||
# Returns the object store service data when it's in the catalog
|
||||
context = context_processors.horizon(self.request)
|
||||
self.assertNotEqual(None, context['object_store_configured'])
|
||||
|
||||
# Returns None when the object store is not in the catalog
|
||||
new_catalog = [service for service in self.request.user.service_catalog
|
||||
if service['type'] != 'object-store']
|
||||
self.request.user.service_catalog = new_catalog
|
||||
context = context_processors.horizon(self.request)
|
||||
self.assertEqual(None, context['object_store_configured'])
|
||||
|
0
horizon/horizon/tests/templates/404.html
Normal file
0
horizon/horizon/tests/templates/404.html
Normal file
21
horizon/horizon/tests/test_panel_urls.py
Normal file
21
horizon/horizon/tests/test_panel_urls.py
Normal file
@ -0,0 +1,21 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', 'horizon.tests.views.fakeView', name='index'),
|
||||
)
|
@ -71,12 +71,6 @@ NOVA_DEFAULT_REGION = 'test'
|
||||
NOVA_ACCESS_KEY = 'test'
|
||||
NOVA_SECRET_KEY = 'test'
|
||||
|
||||
QUANTUM_URL = '127.0.0.1'
|
||||
QUANTUM_PORT = '9696'
|
||||
QUANTUM_TENANT = '1234'
|
||||
QUANTUM_CLIENT_VERSION = '0.1'
|
||||
QUANTUM_ENABLED = True
|
||||
|
||||
CREDENTIAL_AUTHORIZATION_DAYS = 2
|
||||
CREDENTIAL_DOWNLOAD_URL = TESTSERVER + '/credentials/'
|
||||
|
||||
@ -95,11 +89,6 @@ HORIZON_CONFIG = {
|
||||
'default_dashboard': 'nova',
|
||||
}
|
||||
|
||||
SWIFT_ACCOUNT = 'test'
|
||||
SWIFT_USER = 'tester'
|
||||
SWIFT_PASS = 'testing'
|
||||
SWIFT_AUTHURL = 'http://swift/swiftapi/v1.0'
|
||||
|
||||
AVAILABLE_REGIONS = [
|
||||
('http://localhost:5000/v2.0', 'local'),
|
||||
('http://remote:5000/v2.0', 'remote'),
|
||||
|
@ -55,13 +55,6 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
|
||||
# providing a paging element (a "more" link) to paginate results.
|
||||
API_RESULT_LIMIT = 1000
|
||||
|
||||
# Configure quantum connection details for networking
|
||||
QUANTUM_ENABLED = True
|
||||
QUANTUM_URL = '%s' % OPENSTACK_HOST
|
||||
QUANTUM_PORT = '9696'
|
||||
QUANTUM_TENANT = '1234'
|
||||
QUANTUM_CLIENT_VERSION='0.1'
|
||||
|
||||
# If you have external monitoring links, eg:
|
||||
# EXTERNAL_MONITORING = [
|
||||
# ['Nagios','http://foo.com'],
|
||||
|
Loading…
Reference in New Issue
Block a user