¡@

Home 

OpenStack Study: base.py

OpenStack Index

**** CubicPower OpenStack Study ****

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 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.

"""

Contains the core classes and functionality that makes Horizon what it is.

This module is considered internal, and should not be relied on directly.

Public APIs are made available through the :mod:`horizon` module and

the classes contained therein.

"""

import collections

import copy

import inspect

import logging

import os

from django.conf import settings

from django.conf.urls import include # noqa

from django.conf.urls import patterns # noqa

from django.conf.urls import url # noqa

from django.core.exceptions import ImproperlyConfigured # noqa

from django.core.urlresolvers import reverse

from django.utils.datastructures import SortedDict

from django.utils.functional import SimpleLazyObject # noqa

from django.utils.importlib import import_module # noqa

from django.utils.module_loading import module_has_submodule # noqa

from django.utils.translation import ugettext_lazy as _

from horizon import conf

from horizon.decorators import _current_component # noqa

from horizon.decorators import require_auth # noqa

from horizon.decorators import require_perms # noqa

from horizon import loaders

LOG = logging.getLogger(__name__)

**** CubicPower OpenStack Study ****

def _decorate_urlconf(urlpatterns, decorator, *args, **kwargs):

    for pattern in urlpatterns:

        if getattr(pattern, 'callback', None):

            pattern._callback = decorator(pattern.callback, *args, **kwargs)

        if getattr(pattern, 'url_patterns', []):

            _decorate_urlconf(pattern.url_patterns, decorator, *args, **kwargs)

**** CubicPower OpenStack Study ****

class NotRegistered(Exception):

pass

**** CubicPower OpenStack Study ****

class HorizonComponent(object):

**** CubicPower OpenStack Study ****

    def __init__(self):

        super(HorizonComponent, self).__init__()

        if not self.slug:

            raise ImproperlyConfigured('Every %s must have a slug.'

                                       % self.__class__)

**** CubicPower OpenStack Study ****

    def __unicode__(self):

        name = getattr(self, 'name', u"Unnamed %s" % self.__class__.__name__)

        return unicode(name)

**** CubicPower OpenStack Study ****

    def _get_default_urlpatterns(self):

        package_string = '.'.join(self.__module__.split('.')[:-1])

        if getattr(self, 'urls', None):

            try:

                mod = import_module('.%s' % self.urls, package_string)

            except ImportError:

                mod = import_module(self.urls)

            urlpatterns = mod.urlpatterns

        else:

            # Try importing a urls.py from the dashboard package

            if module_has_submodule(import_module(package_string), 'urls'):

                urls_mod = import_module('.urls', package_string)

                urlpatterns = urls_mod.urlpatterns

            else:

                urlpatterns = patterns('')

        return urlpatterns

**** CubicPower OpenStack Study ****

class Registry(object):

**** CubicPower OpenStack Study ****

    def __init__(self):

        self._registry = {}

        if not getattr(self, '_registerable_class', None):

            raise ImproperlyConfigured('Subclasses of Registry must set a '

                                       '"_registerable_class" property.')

**** CubicPower OpenStack Study ****

    def _register(self, cls):

        """Registers the given class.

        If the specified class is already registered then it is ignored.

        """

        if not inspect.isclass(cls):

            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.__name__)

        if cls not in self._registry:

            cls._registered_with = self

            self._registry[cls] = cls()

        return self._registry[cls]

**** CubicPower OpenStack Study ****

    def _unregister(self, cls):

        """Unregisters the given class.

        If the specified class isn't registered, ``NotRegistered`` will

        be raised.

        """

        if not issubclass(cls, self._registerable_class):

            raise ValueError('Only %s classes or subclasses may be '

                             'unregistered.' % self._registerable_class)

        if cls not in self._registry.keys():

            raise NotRegistered('%s is not registered' % cls)

        del self._registry[cls]

        return True

**** CubicPower OpenStack Study ****

    def _registered(self, cls):

        if inspect.isclass(cls) and issubclass(cls, self._registerable_class):

            found = self._registry.get(cls, None)

            if found:

                return found

        else:

            # Allow for fetching by slugs as well.

            for registered in self._registry.values():

                if registered.slug == cls:

                    return registered

        class_name = self._registerable_class.__name__

        if hasattr(self, "_registered_with"):

            parent = self._registered_with._registerable_class.__name__

            raise NotRegistered('%(type)s with slug "%(slug)s" is not '

                                'registered with %(parent)s "%(name)s".'

                                    % {"type": class_name,

                                       "slug": cls,

                                       "parent": parent,

                                       "name": self.slug})

        else:

            slug = getattr(cls, "slug", cls)

            raise NotRegistered('%(type)s with slug "%(slug)s" is not '

                                'registered.' % {"type": class_name,

                                                 "slug": slug})

**** CubicPower OpenStack Study ****

class Panel(HorizonComponent):

"""A base class for

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return "" % self.slug

**** CubicPower OpenStack Study ****

    def get_absolute_url(self):

        """Returns the default URL for this panel.

        The default URL is defined as the URL pattern with ``name="index"`` in

        the URLconf for this panel.

        """

        try:

            return reverse('horizon:%s:%s:%s' % (self._registered_with.slug,

                                                 self.slug,

                                                 self.index_url_name))

        except Exception as exc:

            # Logging here since this will often be called in a template

            # where the exception would be hidden.

            LOG.info("Error reversing absolute URL for %s: %s" % (self, exc))

            raise

    @property

**** CubicPower OpenStack Study ****

    def _decorated_urls(self):

        urlpatterns = self._get_default_urlpatterns()

        # Apply access controls to all views in the patterns

        permissions = getattr(self, 'permissions', [])

        _decorate_urlconf(urlpatterns, require_perms, permissions)

        _decorate_urlconf(urlpatterns, _current_component, panel=self)

        # Return the three arguments to django.conf.urls.include

        return urlpatterns, self.slug, self.slug

**** CubicPower OpenStack Study ****

class PanelGroup(object):

"""A container for a set of :class:`~horizon.Panel` classes.

When iterated, it will yield each of the ``Panel`` instances it

contains.

.. attribute:: slug

A unique string to identify this panel group. Required.

.. attribute:: name

A user-friendly name which will be used as the group heading in

places such as the navigation. Default: ``None``.

.. attribute:: panels

A list of panel module names which should be contained within this

grouping.

"""

**** CubicPower OpenStack Study ****

    def __init__(self, dashboard, slug=None, name=None, panels=None):

        self.dashboard = dashboard

        self.slug = slug or getattr(self, "slug", "default")

        self.name = name or getattr(self, "name", None)

        # Our panels must be mutable so it can be extended by others.

        self.panels = list(panels or getattr(self, "panels", []))

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return "<%s: %s>" % (self.__class__.__name__, self.slug)

**** CubicPower OpenStack Study ****

    def __unicode__(self):

        return self.name

**** CubicPower OpenStack Study ****

    def __iter__(self):

        panel_instances = []

        for name in self.panels:

            try:

                panel_instances.append(self.dashboard.get_panel(name))

            except NotRegistered as e:

                LOG.debug(e)

        return iter(panel_instances)

**** CubicPower OpenStack Study ****

class Dashboard(Registry, HorizonComponent):

"""A base class for

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return "" % self.slug

**** CubicPower OpenStack Study ****

    def __init__(self, *args, **kwargs):

        super(Dashboard, self).__init__(*args, **kwargs)

        self._panel_groups = None

**** CubicPower OpenStack Study ****

    def get_panel(self, panel):

        """Returns the specified :class:`~horizon.Panel` instance registered

        with this dashboard.

        """

        return self._registered(panel)

**** CubicPower OpenStack Study ****

    def get_panels(self):

        """Returns the :class:`~horizon.Panel` instances registered with this

        dashboard in order, without any panel groupings.

        """

        all_panels = []

        panel_groups = self.get_panel_groups()

        for panel_group in panel_groups.values():

            all_panels.extend(panel_group)

        return all_panels

**** CubicPower OpenStack Study ****

    def get_panel_group(self, slug):

        return self._panel_groups[slug]

**** CubicPower OpenStack Study ****

    def get_panel_groups(self):

        registered = copy.copy(self._registry)

        panel_groups = []

        # Gather our known panels

        if self._panel_groups is not None:

            for panel_group in self._panel_groups.values():

                for panel in panel_group:

                    registered.pop(panel.__class__)

                panel_groups.append((panel_group.slug, panel_group))

        # Deal with leftovers (such as add-on registrations)

        if len(registered):

            slugs = [panel.slug for panel in registered.values()]

            new_group = PanelGroup(self,

                                   slug="other",

                                   name=_("Other"),

                                   panels=slugs)

            panel_groups.append((new_group.slug, new_group))

        return SortedDict(panel_groups)

**** CubicPower OpenStack Study ****

    def get_absolute_url(self):

        """Returns the default URL for this dashboard.

        The default URL is defined as the URL pattern with ``name="index"``

        in the URLconf for the :class:`~horizon.Panel` specified by

        :attr:`~horizon.Dashboard.default_panel`.

        """

        try:

            return self._registered(self.default_panel).get_absolute_url()

        except Exception:

            # Logging here since this will often be called in a template

            # where the exception would be hidden.

            LOG.exception("Error reversing absolute URL for %s." % self)

            raise

    @property

**** CubicPower OpenStack Study ****

    def _decorated_urls(self):

        urlpatterns = self._get_default_urlpatterns()

        default_panel = None

        # Add in each panel's views except for the default view.

        for panel in self._registry.values():

            if panel.slug == self.default_panel:

                default_panel = panel

                continue

            url_slug = panel.slug.replace('.', '/')

            urlpatterns += patterns('',

                    url(r'^%s/' % url_slug, include(panel._decorated_urls)))

        # Now the default view, which should come last

        if not default_panel:

            raise NotRegistered('The default panel "%s" is not registered.'

                                % self.default_panel)

        urlpatterns += patterns('',

                url(r'', include(default_panel._decorated_urls)))

        # Require login if not public.

        if not self.public:

            _decorate_urlconf(urlpatterns, require_auth)

        # Apply access controls to all views in the patterns

        permissions = getattr(self, 'permissions', [])

        _decorate_urlconf(urlpatterns, require_perms, permissions)

        _decorate_urlconf(urlpatterns, _current_component, dashboard=self)

        # Return the three arguments to django.conf.urls.include

        return urlpatterns, self.slug, self.slug

**** CubicPower OpenStack Study ****

    def _autodiscover(self):

        """Discovers panels to register from the current dashboard module."""

        if getattr(self, "_autodiscover_complete", False):

            return

        panels_to_discover = []

        panel_groups = []

        # If we have a flat iterable of panel names, wrap it again so

        # we have a consistent structure for the next step.

        if all([isinstance(i, basestring) for i in self.panels]):

            self.panels = [self.panels]

        # Now iterate our panel sets.

        for panel_set in self.panels:

            # Instantiate PanelGroup classes.

            if not isinstance(panel_set, collections.Iterable) and \

                    issubclass(panel_set, PanelGroup):

                panel_group = panel_set(self)

            # Check for nested tuples, and convert them to PanelGroups

            elif not isinstance(panel_set, PanelGroup):

                panel_group = PanelGroup(self, panels=panel_set)

            # Put our results into their appropriate places

            panels_to_discover.extend(panel_group.panels)

            panel_groups.append((panel_group.slug, panel_group))

        self._panel_groups = SortedDict(panel_groups)

        # Do the actual discovery

        package = '.'.join(self.__module__.split('.')[:-1])

        mod = import_module(package)

        for panel in panels_to_discover:

            try:

                before_import_registry = copy.copy(self._registry)

                import_module('.%s.panel' % panel, package)

            except Exception:

                self._registry = before_import_registry

                if module_has_submodule(mod, panel):

                    raise

        self._autodiscover_complete = True

    @classmethod

**** CubicPower OpenStack Study ****

    def register(cls, panel):

        """Registers a :class:`~horizon.Panel` with this dashboard."""

        panel_class = Horizon.register_panel(cls, panel)

        # Support template loading from panel template directories.

        panel_mod = import_module(panel.__module__)

        panel_dir = os.path.dirname(panel_mod.__file__)

        template_dir = os.path.join(panel_dir, "templates")

        if os.path.exists(template_dir):

            key = os.path.join(cls.slug, panel.slug)

            loaders.panel_template_dirs[key] = template_dir

        return panel_class

    @classmethod

**** CubicPower OpenStack Study ****

    def unregister(cls, panel):

        """Unregisters a :class:`~horizon.Panel` from this dashboard."""

        success = Horizon.unregister_panel(cls, panel)

        if success:

            # Remove the panel's template directory.

            key = os.path.join(cls.slug, panel.slug)

            if key in loaders.panel_template_dirs:

                del loaders.panel_template_dirs[key]

        return success

**** CubicPower OpenStack Study ****

class Workflow(object):

**** CubicPower OpenStack Study ****

    def __init__(*args, **kwargs):

        raise NotImplementedError()

try:

    from django.utils.functional import empty  # noqa

except ImportError:

    #Django 1.3 fallback

    empty = None

**** CubicPower OpenStack Study ****

class LazyURLPattern(SimpleLazyObject):

**** CubicPower OpenStack Study ****

    def __iter__(self):

        if self._wrapped is empty:

            self._setup()

        return iter(self._wrapped)

**** CubicPower OpenStack Study ****

    def __reversed__(self):

        if self._wrapped is empty:

            self._setup()

        return reversed(self._wrapped)

**** CubicPower OpenStack Study ****

    def __len__(self):

        if self._wrapped is empty:

            self._setup()

        return len(self._wrapped)

**** CubicPower OpenStack Study ****

    def __getitem__(self, idx):

        if self._wrapped is empty:

            self._setup()

        return self._wrapped[idx]

**** CubicPower OpenStack Study ****

class Site(Registry, HorizonComponent):

"""The overarching class which encompasses all dashboards and panels."""

# Required for registry

_registerable_class = Dashboard

name = "Horizon"

namespace = 'horizon'

slug = 'horizon'

urls = 'horizon.site_urls'

**** CubicPower OpenStack Study ****

    def __repr__(self):

        return u"" % self.slug

    @property

**** CubicPower OpenStack Study ****

    def _conf(self):

        return conf.HORIZON_CONFIG

    @property

**** CubicPower OpenStack Study ****

    def dashboards(self):

        return self._conf['dashboards']

    @property

**** CubicPower OpenStack Study ****

    def default_dashboard(self):

        return self._conf['default_dashboard']

**** CubicPower OpenStack Study ****

    def register(self, dashboard):

        """Registers a :class:`~horizon.Dashboard` with Horizon."""

        return self._register(dashboard)

**** CubicPower OpenStack Study ****

    def unregister(self, dashboard):

        """Unregisters a :class:`~horizon.Dashboard` from Horizon."""

        return self._unregister(dashboard)

**** CubicPower OpenStack Study ****

    def registered(self, dashboard):

        return self._registered(dashboard)

**** CubicPower OpenStack Study ****

    def register_panel(self, dashboard, panel):

        dash_instance = self.registered(dashboard)

        return dash_instance._register(panel)

**** CubicPower OpenStack Study ****

    def unregister_panel(self, dashboard, panel):

        dash_instance = self.registered(dashboard)

        if not dash_instance:

            raise NotRegistered("The dashboard %s is not registered."

                                % dashboard)

        return dash_instance._unregister(panel)

**** CubicPower OpenStack Study ****

    def get_dashboard(self, dashboard):

        """Returns the specified :class:`~horizon.Dashboard` instance."""

        return self._registered(dashboard)

**** CubicPower OpenStack Study ****

    def get_dashboards(self):

        """Returns an ordered tuple of :class:`~horizon.Dashboard` modules.

        Orders dashboards according to the ``"dashboards"`` key in

        ``HORIZON_CONFIG`` or else returns all registered dashboards

        in alphabetical order.

        Any remaining :class:`~horizon.Dashboard` classes registered with

        Horizon but not listed in ``HORIZON_CONFIG['dashboards']``

        will be appended to the end of the list alphabetically.

        """

        if self.dashboards:

            registered = copy.copy(self._registry)

            dashboards = []

            for item in self.dashboards:

                dashboard = self._registered(item)

                dashboards.append(dashboard)

                registered.pop(dashboard.__class__)

            if len(registered):

                extra = registered.values()

                extra.sort()

                dashboards.extend(extra)

            return dashboards

        else:

            dashboards = self._registry.values()

            dashboards.sort()

            return dashboards

**** CubicPower OpenStack Study ****

    def get_default_dashboard(self):

        """Returns the default :class:`~horizon.Dashboard` instance.

        If ``"default_dashboard"`` is specified in ``HORIZON_CONFIG``

        then that dashboard will be returned. If not, the first dashboard

        returned by :func:`~horizon.get_dashboards` will be returned.

        """

        if self.default_dashboard:

            return self._registered(self.default_dashboard)

        elif len(self._registry):

            return self.get_dashboards()[0]

        else:

            raise NotRegistered("No dashboard modules have been registered.")

**** CubicPower OpenStack Study ****

    def get_user_home(self, user):

        """Returns the default URL for a particular user.

        This method can be used to customize where a user is sent when

        they log in, etc. By default it returns the value of

        :meth:`get_absolute_url`.

        An alternative function can be supplied to customize this behavior

        by specifying a either a URL or a function which returns a URL via

        the ``"user_home"`` key in ``HORIZON_CONFIG``. Each of these

        would be valid::

            {"user_home": "/home",}  # A URL

            {"user_home": "my_module.get_user_home",}  # Path to a function

            {"user_home": lambda user: "/" + user.name,}  # A function

            {"user_home": None,}  # Will always return the default dashboard

        This can be useful if the default dashboard may not be accessible

        to all users. When user_home is missing from HORIZON_CONFIG,

        it will default to the settings.LOGIN_REDIRECT_URL value.

        """

        user_home = self._conf['user_home']

        if user_home:

            if callable(user_home):

                return user_home(user)

            elif isinstance(user_home, basestring):

                # Assume we've got a URL if there's a slash in it

                if user_home.find("/") != -1:

                    return user_home

                else:

                    mod, func = user_home.rsplit(".", 1)

                    return getattr(import_module(mod), func)(user)

            # If it's not callable and not a string, it's wrong.

            raise ValueError('The user_home setting must be either a string '

                             'or a callable object (e.g. a function).')

        else:

            return self.get_absolute_url()

**** CubicPower OpenStack Study ****

    def get_absolute_url(self):

        """Returns the default URL for Horizon's URLconf.

        The default URL is determined by calling

        :meth:`~horizon.Dashboard.get_absolute_url`

        on the :class:`~horizon.Dashboard` instance returned by

        :meth:`~horizon.get_default_dashboard`.

        """

        return self.get_default_dashboard().get_absolute_url()

    @property

**** CubicPower OpenStack Study ****

    def _lazy_urls(self):

        """Lazy loading for URL patterns.

        This method avoids problems associated with attempting to evaluate

        the the URLconf before the settings module has been loaded.

        """

        def url_patterns():

            return self._urls()[0]

        return LazyURLPattern(url_patterns), self.namespace, self.slug

**** CubicPower OpenStack Study ****

        def url_patterns():

            return self._urls()[0]

        return LazyURLPattern(url_patterns), self.namespace, self.slug

**** CubicPower OpenStack Study ****

    def _urls(self):

        """Constructs the URLconf for Horizon from registered Dashboards."""

        urlpatterns = self._get_default_urlpatterns()

        self._autodiscover()

        # Discover each dashboard's panels.

        for dash in self._registry.values():

            dash._autodiscover()

        # Load the plugin-based panel configuration

        self._load_panel_customization()

        # Allow for override modules

        if self._conf.get("customization_module", None):

            customization_module = self._conf["customization_module"]

            bits = customization_module.split('.')

            mod_name = bits.pop()

            package = '.'.join(bits)

            mod = import_module(package)

            try:

                before_import_registry = copy.copy(self._registry)

                import_module('%s.%s' % (package, mod_name))

            except Exception:

                self._registry = before_import_registry

                if module_has_submodule(mod, mod_name):

                    raise

        # Compile the dynamic urlconf.

        for dash in self._registry.values():

            urlpatterns += patterns('',

                    url(r'^%s/' % dash.slug, include(dash._decorated_urls)))

        # Return the three arguments to django.conf.urls.include

        return urlpatterns, self.namespace, self.slug

**** CubicPower OpenStack Study ****

    def _autodiscover(self):

        """Discovers modules to register from ``settings.INSTALLED_APPS``.

        This makes sure that the appropriate modules get imported to register

        themselves with Horizon.

        """

        if not getattr(self, '_registerable_class', None):

            raise ImproperlyConfigured('You must set a '

                                       '"_registerable_class" property '

                                       'in order to use autodiscovery.')

        # Discover both dashboards and panels, in that order

        for mod_name in ('dashboard', 'panel'):

            for app in settings.INSTALLED_APPS:

                mod = import_module(app)

                try:

                    before_import_registry = copy.copy(self._registry)

                    import_module('%s.%s' % (app, mod_name))

                except Exception:

                    self._registry = before_import_registry

                    if module_has_submodule(mod, mod_name):

                        raise

**** CubicPower OpenStack Study ****

    def _load_panel_customization(self):

        """Applies the plugin-based panel configurations.

        This method parses the panel customization from the ``HORIZON_CONFIG``

        and make changes to the dashboard accordingly.

        It supports adding, removing and setting default panels on the

        dashboard. It also support registering a panel group.

        """

        panel_customization = self._conf.get("panel_customization", [])

        for config in panel_customization:

            if config.get('PANEL'):

                self._process_panel_configuration(config)

            elif config.get('PANEL_GROUP'):

                self._process_panel_group_configuration(config)

            else:

                LOG.warning("Skipping %s because it doesn't have PANEL or "

                            "PANEL_GROUP defined.", config.__name__)

**** CubicPower OpenStack Study ****

    def _process_panel_configuration(self, config):

        """Add, remove and set default panels on the dashboard."""

        try:

            dashboard = config.get('PANEL_DASHBOARD')

            if not dashboard:

                LOG.warning("Skipping %s because it doesn't have "

                            "PANEL_DASHBOARD defined.", config.__name__)

                return

            panel_slug = config.get('PANEL')

            dashboard_cls = self.get_dashboard(dashboard)

            panel_group = config.get('PANEL_GROUP')

            default_panel = config.get('DEFAULT_PANEL')

            # Set the default panel

            if default_panel:

                dashboard_cls.default_panel = default_panel

            # Remove the panel

            if config.get('REMOVE_PANEL', False):

                for panel in dashboard_cls.get_panels():

                    if panel_slug == panel.slug:

                        dashboard_cls.unregister(panel.__class__)

            elif config.get('ADD_PANEL', None):

                # Add the panel to the dashboard

                panel_path = config['ADD_PANEL']

                mod_path, panel_cls = panel_path.rsplit(".", 1)

                try:

                    mod = import_module(mod_path)

                except ImportError:

                    LOG.warning("Could not load panel: %s", mod_path)

                    return

                panel = getattr(mod, panel_cls)

                dashboard_cls.register(panel)

                if panel_group:

                    dashboard_cls.get_panel_group(panel_group).\

                        panels.append(panel.slug)

                else:

                    panels = list(dashboard_cls.panels)

                    panels.append(panel)

                    dashboard_cls.panels = tuple(panels)

        except Exception as e:

            LOG.warning('Could not process panel %(panel)s: %(exc)s',

                        {'panel': panel_slug, 'exc': e})

**** CubicPower OpenStack Study ****

    def _process_panel_group_configuration(self, config):

        """Adds a panel group to the dashboard."""

        panel_group_slug = config.get('PANEL_GROUP')

        try:

            dashboard = config.get('PANEL_GROUP_DASHBOARD')

            if not dashboard:

                LOG.warning("Skipping %s because it doesn't have "

                            "PANEL_GROUP_DASHBOARD defined.", config.__name__)

                return

            dashboard_cls = self.get_dashboard(dashboard)

            panel_group_name = config.get('PANEL_GROUP_NAME')

            if not panel_group_name:

                LOG.warning("Skipping %s because it doesn't have "

                            "PANEL_GROUP_NAME defined.", config.__name__)

                return

            # Create the panel group class

            panel_group = type(panel_group_slug,

                               (PanelGroup, ),

                               {'slug': panel_group_slug,

                                'name': panel_group_name},)

            # Add the panel group to dashboard

            panels = list(dashboard_cls.panels)

            panels.append(panel_group)

            dashboard_cls.panels = tuple(panels)

            # Trigger the autodiscovery to completely load the new panel group

            dashboard_cls._autodiscover_complete = False

            dashboard_cls._autodiscover()

        except Exception as e:

            LOG.warning('Could not process panel group %(panel_group)s: '

                        '%(exc)s',

                        {'panel_group': panel_group_slug, 'exc': e})

**** CubicPower OpenStack Study ****

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

**** CubicPower OpenStack Study ****

    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 = HorizonSite()