diff --git a/heat_dashboard/api/__init__.py b/heat_dashboard/api/__init__.py index 4d3bf13..53a07bd 100644 --- a/heat_dashboard/api/__init__.py +++ b/heat_dashboard/api/__init__.py @@ -31,7 +31,7 @@ In other words, Horizon developers not working on openstack_dashboard.api shouldn't need to understand the finer details of APIs for Keystone/Nova/Glance/Swift et. al. """ -from openstack_dashboard.api import heat +from heat_dashboard.api import heat __all__ = [ diff --git a/heat_dashboard/api/rest/__init__.py b/heat_dashboard/api/rest/__init__.py index 925cbb0..e958c20 100644 --- a/heat_dashboard/api/rest/__init__.py +++ b/heat_dashboard/api/rest/__init__.py @@ -21,27 +21,9 @@ It does not promise to adhere to the general OpenStack API Guidelines set out in https://wiki.openstack.org/wiki/APIChangeGuidelines. """ -from openstack_dashboard.api.rest import cinder -from openstack_dashboard.api.rest import config -from openstack_dashboard.api.rest import glance -from openstack_dashboard.api.rest import heat -from openstack_dashboard.api.rest import keystone -from openstack_dashboard.api.rest import network -from openstack_dashboard.api.rest import neutron -from openstack_dashboard.api.rest import nova -from openstack_dashboard.api.rest import policy -from openstack_dashboard.api.rest import swift +from heat_dashboard.api.rest import heat __all__ = [ - 'cinder', - 'config', - 'glance', 'heat', - 'keystone', - 'network', - 'neutron', - 'nova', - 'policy', - 'swift', ] diff --git a/heat_dashboard/api/rest/heat.py b/heat_dashboard/api/rest/heat.py index 106363c..6cfd5ed 100644 --- a/heat_dashboard/api/rest/heat.py +++ b/heat_dashboard/api/rest/heat.py @@ -13,8 +13,9 @@ from django.views import generic -# from openstack_dashboard import api from heat_dashboard import api + +from openstack_dashboard import api as dashboard_api from openstack_dashboard.api.rest import urls from openstack_dashboard.api.rest import utils as rest_utils @@ -45,7 +46,7 @@ class Services(generic.View): @rest_utils.ajax() def get(self, request): """Get a list of heat services.""" - if api.base.is_service_enabled(request, 'orchestration'): + if dashboard_api.base.is_service_enabled(request, 'orchestration'): result = api.heat.service_list(request) return {'items': [u.to_dict() for u in result]} else: diff --git a/heat_dashboard/content/resource_types/tabs.py b/heat_dashboard/content/resource_types/tabs.py index 03b4690..836e14d 100644 --- a/heat_dashboard/content/resource_types/tabs.py +++ b/heat_dashboard/content/resource_types/tabs.py @@ -19,7 +19,6 @@ from horizon import tabs class ResourceTypeOverviewTab(tabs.Tab): name = _("Overview") slug = "resource_type_overview" - # template_name = "project/stacks.resource_types/_details.html" template_name = "project/resource_types/_details.html" def get_context_data(self, request): diff --git a/heat_dashboard/content/resource_types/views.py b/heat_dashboard/content/resource_types/views.py index b699b24..dc6f6bf 100644 --- a/heat_dashboard/content/resource_types/views.py +++ b/heat_dashboard/content/resource_types/views.py @@ -20,11 +20,6 @@ from horizon import exceptions from horizon import tables from horizon import tabs -# from openstack_dashboard import api -# import openstack_dashboard.dashboards.project.stacks.resource_types.tables \ -# as project_tables -# import openstack_dashboard.dashboards.project.stacks.resource_types.tabs \ -# as project_tabs from heat_dashboard import api import heat_dashboard.content.resource_types.tables as project_tables import heat_dashboard.content.resource_types.tabs as project_tabs diff --git a/heat_dashboard/content/stacks/api.py b/heat_dashboard/content/stacks/api.py index ce5affd..e8829c5 100644 --- a/heat_dashboard/content/stacks/api.py +++ b/heat_dashboard/content/stacks/api.py @@ -12,10 +12,10 @@ import json -from openstack_dashboard.api import heat +from heat_dashboard.api import heat -from openstack_dashboard.dashboards.project.stacks import mappings -from openstack_dashboard.dashboards.project.stacks import sro +from heat_dashboard.content.stacks import mappings +from heat_dashboard.content.stacks import sro class Stack(object): diff --git a/heat_dashboard/content/stacks/forms.py b/heat_dashboard/content/stacks/forms.py index 7ec66b3..6851369 100644 --- a/heat_dashboard/content/stacks/forms.py +++ b/heat_dashboard/content/stacks/forms.py @@ -26,7 +26,6 @@ from horizon import exceptions from horizon import forms from horizon import messages -# from openstack_dashboard import api from heat_dashboard import api from openstack_dashboard.dashboards.project.images \ import utils as image_utils diff --git a/heat_dashboard/content/stacks/tables.py b/heat_dashboard/content/stacks/tables.py index 45809a0..708dc4b 100644 --- a/heat_dashboard/content/stacks/tables.py +++ b/heat_dashboard/content/stacks/tables.py @@ -23,8 +23,6 @@ from horizon.utils import filters from heatclient import exc -# from openstack_dashboard import api -# from openstack_dashboard.dashboards.project.stacks import mappings from heat_dashboard import api from heat_dashboard.content.stacks import mappings @@ -160,7 +158,6 @@ class DeleteStack(tables.DeleteAction): policy_rules = (("orchestration", "stacks:delete"),) def delete(self, request, stack_id): - # api.heat.stack_delete(request, stack_id) api.heat.stack_delete(request, stack_id) def allowed(self, request, stack): @@ -177,7 +174,6 @@ class StacksUpdateRow(tables.Row): def get_data(self, request, stack_id): try: - # stack = api.heat.stack_get(request, stack_id) stack = api.heat.stack_get(request, stack_id) if stack.stack_status == 'DELETE_COMPLETE': # returning 404 to the ajax call removes the diff --git a/heat_dashboard/content/stacks/tabs.py b/heat_dashboard/content/stacks/tabs.py index a6508ff..11e91c2 100644 --- a/heat_dashboard/content/stacks/tabs.py +++ b/heat_dashboard/content/stacks/tabs.py @@ -55,7 +55,6 @@ class StackOverviewTab(tabs.Tab): name = _("Overview") slug = "overview" template_name = "project/stacks/_detail_overview.html" - # template_name = "stacks/_detail_overview.html" def allowed(self, request): return policy.check( @@ -72,7 +71,6 @@ class ResourceOverviewTab(tabs.Tab): name = _("Overview") slug = "resource_overview" template_name = "project/stacks/_resource_overview.html" - # template_name = "stacks/_resource_overview.html" def get_context_data(self, request): resource = self.tab_group.kwargs['resource'] @@ -87,7 +85,6 @@ class StackEventsTab(tabs.Tab): name = _("Events") slug = "events" template_name = "project/stacks/_detail_events.html" - # template_name = "stacks/_detail_events.html" preload = False def allowed(self, request): @@ -119,7 +116,6 @@ class StackResourcesTab(tabs.Tab): name = _("Resources") slug = "resources" template_name = "project/stacks/_detail_resources.html" - # template_name = "stacks/_detail_resources.html" preload = False def allowed(self, request): @@ -152,7 +148,6 @@ class StackTemplateTab(tabs.Tab): name = _("Template") slug = "stack_template" template_name = "project/stacks/_stack_template.html" - # template_name = "stacks/_stack_template.html" def allowed(self, request): return policy.check( diff --git a/heat_dashboard/content/template_generator/panel.py b/heat_dashboard/content/template_generator/panel.py index 1a59fdb..3a670e9 100644 --- a/heat_dashboard/content/template_generator/panel.py +++ b/heat_dashboard/content/template_generator/panel.py @@ -15,13 +15,9 @@ from django.utils.translation import ugettext_lazy as _ import horizon -# from openstack_dashboard.dashboards.project import dashboard class TemplateGenerator(horizon.Panel): name = _("Template Generator") slug = 'template_generator' permissions = ('openstack.services.orchestration',) - - -# dashboard.Project.register(TemplateGenerator) diff --git a/heat_dashboard/content/template_versions/tabs.py b/heat_dashboard/content/template_versions/tabs.py index c44575e..a9280c7 100644 --- a/heat_dashboard/content/template_versions/tabs.py +++ b/heat_dashboard/content/template_versions/tabs.py @@ -15,11 +15,10 @@ from django.utils.translation import ugettext_lazy as _ from horizon import messages from horizon import tabs -from openstack_dashboard import api from openstack_dashboard import policy -# from openstack_dashboard.dashboards.project.stacks.template_versions \ -# import tables as project_tables +from heat_dashboard import api + from heat_dashboard.content.template_versions import tables as project_tables diff --git a/heat_dashboard/content/template_versions/urls.py b/heat_dashboard/content/template_versions/urls.py index e956926..b3dcd0f 100644 --- a/heat_dashboard/content/template_versions/urls.py +++ b/heat_dashboard/content/template_versions/urls.py @@ -13,8 +13,6 @@ from django.conf.urls import url -# from openstack_dashboard.dashboards.project.stacks.template_versions \ -# import views from heat_dashboard.content.template_versions import views diff --git a/heat_dashboard/content/template_versions/views.py b/heat_dashboard/content/template_versions/views.py index 9a674be..3c16838 100644 --- a/heat_dashboard/content/template_versions/views.py +++ b/heat_dashboard/content/template_versions/views.py @@ -18,11 +18,6 @@ from horizon import exceptions from horizon import tables from horizon import tabs -# from openstack_dashboard import api -# import openstack_dashboard.dashboards.project.\ -# stacks.template_versions.tables as project_tables -# import openstack_dashboard.dashboards.project.\ -# stacks.template_versions.tabs as project_tabs from heat_dashboard import api import heat_dashboard.content.template_versions.tables as project_tables import heat_dashboard.content.template_versions.tabs as project_tabs diff --git a/heat_dashboard/enabled/_1620_project_stacks_panel.py b/heat_dashboard/enabled/_1620_project_stacks_panel.py index 82791c3..8718bef 100644 --- a/heat_dashboard/enabled/_1620_project_stacks_panel.py +++ b/heat_dashboard/enabled/_1620_project_stacks_panel.py @@ -22,5 +22,3 @@ ADD_PANEL = 'heat_dashboard.content.stacks.panel.Stacks' # Automatically discover static resources in installed apps AUTO_DISCOVER_STATIC_FILES = True - -# ADD_INSTALLED_APPS = ['heat_dashboard.content.stacks', ] diff --git a/heat_dashboard/enabled/_1640_project_template_versions_panel.py b/heat_dashboard/enabled/_1640_project_template_versions_panel.py index d914326..600276c 100644 --- a/heat_dashboard/enabled/_1640_project_template_versions_panel.py +++ b/heat_dashboard/enabled/_1640_project_template_versions_panel.py @@ -22,5 +22,3 @@ ADD_PANEL = 'heat_dashboard.content.template_versions.panel.TemplateVersions' # Automatically discover static resources in installed apps AUTO_DISCOVER_STATIC_FILES = True - -# ADD_INSTALLED_APPS = ['heat_dashboard.content.template_versions', ] diff --git a/heat_dashboard/test/helpers.py b/heat_dashboard/test/helpers.py index 24af4fa..c4ec1ca 100644 --- a/heat_dashboard/test/helpers.py +++ b/heat_dashboard/test/helpers.py @@ -16,8 +16,6 @@ # License for the specific language governing permissions and limitations # under the License. -import collections -import copy from functools import wraps from importlib import import_module import os @@ -28,34 +26,27 @@ import django from django.conf import settings from django.contrib.messages.storage import default_storage from django.core.handlers import wsgi -from django.core import urlresolvers from django.test.client import RequestFactory -from django.test import utils as django_test_utils from django.utils import http -from cinderclient import client as cinder_client -import glanceclient from heatclient import client as heat_client from keystoneclient.v2_0 import client as keystone_client import mock -from mox3 import mox from neutronclient.v2_0 import client as neutron_client -from novaclient import api_versions as nova_api_versions -from novaclient.v2 import client as nova_client from openstack_auth import user from openstack_auth import utils from requests.packages.urllib3.connection import HTTPConnection + import six from six import moves -from swiftclient import client as swift_client -from horizon import base -from horizon import conf from horizon.test import helpers as horizon_helpers -from openstack_dashboard import api -from openstack_dashboard import context_processors -from openstack_dashboard.test.test_data import utils as test_utils +from openstack_dashboard import api as project_api +from openstack_dashboard import context_processors + +from heat_dashboard import api +from heat_dashboard.test.test_data import utils as test_utils # Makes output of failing mox tests much easier to read. wsgi.WSGIRequest.__repr__ = lambda self: "" @@ -412,63 +403,20 @@ class APITestCase(TestCase): """ return self.stub_keystoneclient() - def fake_glanceclient(request, version='1'): - """Returns the stub glanceclient. - - Only necessary because the function takes too many arguments to - conveniently be a lambda. - """ - return self.stub_glanceclient() - - def fake_novaclient(request, version=None): - return self.stub_novaclient() - # Store the original clients - self._original_glanceclient = api.glance.glanceclient - self._original_keystoneclient = api.keystone.keystoneclient - self._original_novaclient = api.nova.novaclient - self._original_neutronclient = api.neutron.neutronclient - self._original_cinderclient = api.cinder.cinderclient + self._original_keystoneclient = project_api.keystone.keystoneclient self._original_heatclient = api.heat.heatclient # Replace the clients with our stubs. - api.glance.glanceclient = fake_glanceclient - api.keystone.keystoneclient = fake_keystoneclient - api.nova.novaclient = fake_novaclient - api.neutron.neutronclient = lambda request: self.stub_neutronclient() - api.cinder.cinderclient = lambda request: self.stub_cinderclient() + project_api.keystone.keystoneclient = fake_keystoneclient api.heat.heatclient = (lambda request, password=None: self.stub_heatclient()) def tearDown(self): super(APITestCase, self).tearDown() - api.glance.glanceclient = self._original_glanceclient - api.nova.novaclient = self._original_novaclient - api.keystone.keystoneclient = self._original_keystoneclient - api.neutron.neutronclient = self._original_neutronclient - api.cinder.cinderclient = self._original_cinderclient + project_api.keystone.keystoneclient = self._original_keystoneclient api.heat.heatclient = self._original_heatclient - def stub_novaclient(self): - if not hasattr(self, "novaclient"): - self.mox.StubOutWithMock(nova_client, 'Client') - # mock the api_version since MockObject.__init__ ignores it. - # The preferred version in the api.nova code is 2 but that's - # equivalent to 2.1 now and is the base version that's backward - # compatible to 2.0 anyway. - api_version = nova_api_versions.APIVersion('2.1') - nova_client.Client.api_version = api_version - nova_client.Client.projectid = 'fake_project' - nova_client.Client.tenant_id = 'fake_tenant' - self.novaclient = self.mox.CreateMock(nova_client.Client) - return self.novaclient - - def stub_cinderclient(self): - if not hasattr(self, "cinderclient"): - self.mox.StubOutWithMock(cinder_client, 'Client') - self.cinderclient = self.mox.CreateMock(cinder_client.Client) - return self.cinderclient - def stub_keystoneclient(self): if not hasattr(self, "keystoneclient"): self.mox.StubOutWithMock(keystone_client, 'Client') @@ -482,35 +430,12 @@ class APITestCase(TestCase): self.keystoneclient = self.mox.CreateMock(keystone_client.Client) return self.keystoneclient - def stub_glanceclient(self): - if not hasattr(self, "glanceclient"): - self.mox.StubOutWithMock(glanceclient, 'Client') - self.glanceclient = self.mox.CreateMock(glanceclient.Client) - return self.glanceclient - def stub_neutronclient(self): if not hasattr(self, "neutronclient"): self.mox.StubOutWithMock(neutron_client, 'Client') self.neutronclient = self.mox.CreateMock(neutron_client.Client) return self.neutronclient - def stub_swiftclient(self, expected_calls=1): - if not hasattr(self, "swiftclient"): - self.mox.StubOutWithMock(swift_client, 'Connection') - self.swiftclient = self.mox.CreateMock(swift_client.Connection) - while expected_calls: - swift_client.Connection(None, - mox.IgnoreArg(), - None, - preauthtoken=mox.IgnoreArg(), - preauthurl=mox.IgnoreArg(), - cacert=None, - insecure=False, - auth_version="2.0") \ - .AndReturn(self.swiftclient) - expected_calls -= 1 - return self.swiftclient - def stub_heatclient(self): if not hasattr(self, "heatclient"): self.mox.StubOutWithMock(heat_client, 'Client') @@ -522,161 +447,8 @@ class APITestCase(TestCase): class ResetImageAPIVersionMixin(object): def setUp(self): super(ResetImageAPIVersionMixin, self).setUp() - api.glance.VERSIONS.clear_active_cache() + project_api.glance.VERSIONS.clear_active_cache() def tearDown(self): - api.glance.VERSIONS.clear_active_cache() + project_api.glance.VERSIONS.clear_active_cache() super(ResetImageAPIVersionMixin, self).tearDown() - - -@unittest.skipUnless(os.environ.get('WITH_SELENIUM', False), - "The WITH_SELENIUM env variable is not set.") -class SeleniumTestCase(horizon_helpers.SeleniumTestCase): - - def setUp(self): - super(SeleniumTestCase, self).setUp() - - test_utils.load_test_data(self) - self.mox = mox.Mox() - - self._real_get_user = utils.get_user - self.setActiveUser(id=self.user.id, - token=self.token, - username=self.user.name, - tenant_id=self.tenant.id, - service_catalog=self.service_catalog, - authorized_tenants=self.tenants.list()) - self.patchers = _apply_panel_mocks() - os.environ["HORIZON_TEST_RUN"] = "True" - - def tearDown(self): - self.mox.UnsetStubs() - utils.get_user = self._real_get_user - mock.patch.stopall() - self.mox.VerifyAll() - del os.environ["HORIZON_TEST_RUN"] - - def setActiveUser(self, id=None, token=None, username=None, tenant_id=None, - service_catalog=None, tenant_name=None, roles=None, - authorized_tenants=None, enabled=True): - def get_user(request): - return user.User(id=id, - token=token, - user=username, - tenant_id=tenant_id, - service_catalog=service_catalog, - roles=roles, - enabled=enabled, - authorized_tenants=authorized_tenants, - endpoint=settings.OPENSTACK_KEYSTONE_URL) - utils.get_user = get_user - - -class SeleniumAdminTestCase(SeleniumTestCase): - """Version of AdminTestCase for Selenium. - - Sets an active user with the "admin" role for testing admin-only views and - functionality. - """ - def setActiveUser(self, *args, **kwargs): - if "roles" not in kwargs: - kwargs['roles'] = [self.roles.admin._info] - super(SeleniumAdminTestCase, self).setActiveUser(*args, **kwargs) - - -def my_custom_sort(flavor): - sort_order = { - 'm1.secret': 0, - 'm1.tiny': 1, - 'm1.massive': 2, - 'm1.metadata': 3, - } - return sort_order[flavor.name] - - -class PluginTestCase(TestCase): - """Test case for testing plugin system of Horizon. - - For use with tests which deal with the pluggable dashboard and panel - configuration, it takes care of backing up and restoring the Horizon - configuration. - """ - def setUp(self): - super(PluginTestCase, self).setUp() - self.old_horizon_config = conf.HORIZON_CONFIG - conf.HORIZON_CONFIG = conf.LazySettings() - 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(PluginTestCase, self).tearDown() - conf.HORIZON_CONFIG = self.old_horizon_config - # 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__ - moves.reload_module(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) - self._reload_urls() - - def _reload_urls(self): - """CLeans up URLs. - - 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() - moves.reload_module(import_module(settings.ROOT_URLCONF)) - base.Horizon._urls() - - -class update_settings(django_test_utils.override_settings): - """override_settings which allows override an item in dict. - - django original override_settings replaces a dict completely, - however OpenStack dashboard setting has many dictionary configuration - and there are test case where we want to override only one item in - a dictionary and keep other items in the dictionary. - This version of override_settings allows this if keep_dict is True. - - If keep_dict False is specified, the original behavior of - Django override_settings is used. - """ - - def __init__(self, keep_dict=True, **kwargs): - if keep_dict: - for key, new_value in kwargs.items(): - value = getattr(settings, key, None) - if (isinstance(new_value, collections.Mapping) and - isinstance(value, collections.Mapping)): - copied = copy.copy(value) - copied.update(new_value) - kwargs[key] = copied - super(update_settings, self).__init__(**kwargs) - - -def mock_obj_to_dict(r): - return mock.Mock(**{'to_dict.return_value': r}) - - -def mock_factory(r): - """mocks all the attributes as well as the to_dict """ - mocked = mock_obj_to_dict(r) - mocked.configure_mock(**r) - return mocked diff --git a/heat_dashboard/test/settings.py b/heat_dashboard/test/settings.py index ed94fd4..23e9093 100644 --- a/heat_dashboard/test/settings.py +++ b/heat_dashboard/test/settings.py @@ -11,14 +11,29 @@ # License for the specific language governing permissions and limitations # under the License. -from horizon.test.settings import * # noqa -from openstack_dashboard.test.settings import * # noqa +# Default to Horizons test settings to avoid any missing keys +import heat_dashboard.enabled + +from openstack_dashboard.test.settings import * # noqa: F403,H303 + +from openstack_dashboard.utils import settings -INSTALLED_APPS = list(INSTALLED_APPS) -INSTALLED_APPS.append('heat_dashboard.content.stacks') -INSTALLED_APPS.append('heat_dashboard.content.resource_types') -INSTALLED_APPS.append('heat_dashboard.content.template_versions') +# pop these keys to avoid log warnings about deprecation +# update_dashboards will populate them anyway +HORIZON_CONFIG.pop('dashboards', None) +HORIZON_CONFIG.pop('default_dashboard', None) -# further implementation -# INSTALLED_APPS.append('heat_dashboard.content.template_generator') +# Update the dashboards with heat_dashboard enabled files +# and current INSTALLED_APPS +settings.update_dashboards( + [ + openstack_dashboard.enabled, + heat_dashboard.enabled, + ], + HORIZON_CONFIG, + INSTALLED_APPS +) + +# Remove duplicated apps +INSTALLED_APPS = list(set(INSTALLED_APPS)) diff --git a/heat_dashboard/test/test_data/__init__.py b/heat_dashboard/test/test_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/heat_dashboard/test/test_data/exceptions.py b/heat_dashboard/test/test_data/exceptions.py new file mode 100644 index 0000000..8b4c31f --- /dev/null +++ b/heat_dashboard/test/test_data/exceptions.py @@ -0,0 +1,56 @@ +# 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. + +import heatclient.exc as heat_exceptions +import six + +from heat_dashboard.test.test_data import utils + + +def create_stubbed_exception(cls, status_code=500): + msg = "Expected failure." + + def fake_init_exception(self, code=None, message=None, **kwargs): + if code is not None: + if hasattr(self, 'http_status'): + self.http_status = code + else: + self.code = code + self.message = message or self.__class__.message + + try: + # Neutron sometimes updates the message with additional + # information, like a reason. + self.message = self.message % kwargs + except Exception: + pass # We still have the main error message. + + def fake_str(self): + return str(self.message) + + def fake_unicode(self): + return six.text_type(self.message) + + cls.__init__ = fake_init_exception + cls.__str__ = fake_str + cls.__unicode__ = fake_unicode + cls.silence_logging = True + return cls(status_code, msg) + + +def data(TEST): + TEST.exceptions = utils.TestDataContainer() + + heat_exception = heat_exceptions.HTTPException + TEST.exceptions.heat = create_stubbed_exception(heat_exception) diff --git a/heat_dashboard/test/test_data/heat_data.py b/heat_dashboard/test/test_data/heat_data.py new file mode 100644 index 0000000..bc51013 --- /dev/null +++ b/heat_dashboard/test/test_data/heat_data.py @@ -0,0 +1,617 @@ +# 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 heatclient.v1 import resource_types +from heatclient.v1 import resources +from heatclient.v1 import services +from heatclient.v1 import stacks +from heatclient.v1 import template_versions + +from heat_dashboard.test.test_data import utils + +# A slightly hacked up copy of a sample cloudformation template for testing. +TEMPLATE = """ +{ +"AWSTemplateFormatVersion": "2010-09-09", +"Description": "AWS CloudFormation Sample Template.", +"Parameters": { +"KeyName": { +"Description": "Name of an EC2 Key Pair to enable SSH access to the instances", +"Type": "String" +}, +"InstanceType": { +"Description": "WebServer EC2 instance type", +"Type": "String", +"Default": "m1.small", +"AllowedValues": [ +"m1.tiny", +"m1.small", +"m1.medium", +"m1.large", +"m1.xlarge" +], +"ConstraintDescription": "must be a valid EC2 instance type." +}, +"DBName": { +"Default": "wordpress", +"Description": "The WordPress database name", +"Type": "String", +"MinLength": "1", +"MaxLength": "64", +"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", +"ConstraintDescription": "must begin with a letter and..." +}, +"DBUsername": { +"Default": "admin", +"NoEcho": "true", +"Description": "The WordPress database admin account username", +"Type": "String", +"MinLength": "1", +"MaxLength": "16", +"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", +"ConstraintDescription": "must begin with a letter and..." +}, +"DBPassword": { +"Default": "admin", +"NoEcho": "true", +"Description": "The WordPress database admin account password", +"Type": "String", +"MinLength": "1", +"MaxLength": "41", +"AllowedPattern": "[a-zA-Z0-9]*", +"ConstraintDescription": "must contain only alphanumeric characters." +}, +"DBRootPassword": { +"Default": "admin", +"NoEcho": "true", +"Description": "Root password for MySQL", +"Type": "String", +"MinLength": "1", +"MaxLength": "41", +"AllowedPattern": "[a-zA-Z0-9]*", +"ConstraintDescription": "must contain only alphanumeric characters." +}, +"LinuxDistribution": { +"Default": "F17", +"Description": "Distribution of choice", +"Type": "String", +"AllowedValues": [ +"F18", +"F17", +"U10", +"RHEL-6.1", +"RHEL-6.2", +"RHEL-6.3" +] +}, +"Network": { +"Type": "String", +"CustomConstraint": "neutron.network" +} +}, +"Mappings": { +"AWSInstanceType2Arch": { +"m1.tiny": { +"Arch": "32" +}, +"m1.small": { +"Arch": "64" +}, +"m1.medium": { +"Arch": "64" +}, +"m1.large": { +"Arch": "64" +}, +"m1.xlarge": { +"Arch": "64" +} +}, +"DistroArch2AMI": { +"F18": { +"32": "F18-i386-cfntools", +"64": "F18-x86_64-cfntools" +}, +"F17": { +"32": "F17-i386-cfntools", +"64": "F17-x86_64-cfntools" +}, +"U10": { +"32": "U10-i386-cfntools", +"64": "U10-x86_64-cfntools" +}, +"RHEL-6.1": { +"32": "rhel61-i386-cfntools", +"64": "rhel61-x86_64-cfntools" +}, +"RHEL-6.2": { +"32": "rhel62-i386-cfntools", +"64": "rhel62-x86_64-cfntools" +}, +"RHEL-6.3": { +"32": "rhel63-i386-cfntools", +"64": "rhel63-x86_64-cfntools" +} +} +}, +"Resources": { +"WikiDatabase": { +"Type": "AWS::EC2::Instance", +"Metadata": { +"AWS::CloudFormation::Init": { +"config": { +"packages": { +"yum": { +"mysql": [], +"mysql-server": [], +"httpd": [], +"wordpress": [] +} +}, +"services": { +"systemd": { +"mysqld": { +"enabled": "true", +"ensureRunning": "true" +}, +"httpd": { +"enabled": "true", +"ensureRunning": "true" +} +} +} +} +} +}, +"Properties": { +"ImageId": { +"Fn::FindInMap": [ +"DistroArch2AMI", +{ +"Ref": "LinuxDistribution" +}, +{ +"Fn::FindInMap": [ +"AWSInstanceType2Arch", +{ +"Ref": "InstanceType" +}, +"Arch" +] +} +] +}, +"InstanceType": { +"Ref": "InstanceType" +}, +"KeyName": { +"Ref": "KeyName" +}, +"UserData": { +"Fn::Base64": { +"Fn::Join": [ +"", +[ +"#!/bin/bash -v\\n", +"/opt/aws/bin/cfn-init\\n" +] +] +} +} +} +} +}, +"Outputs": { +"WebsiteURL": { +"Value": { +"Fn::Join": [ +"", +[ +"http://", +{ +"Fn::GetAtt": [ +"WikiDatabase", +"PublicIp" +] +}, +"/wordpress" +] +] +}, +"Description": "URL for Wordpress wiki" +} +} +} +""" + +VALIDATE = """ +{ +"Description": "AWS CloudFormation Sample Template.", +"Parameters": { +"DBUsername": { +"Type": "String", +"Description": "The WordPress database admin account username", +"Default": "admin", +"MinLength": "1", +"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", +"NoEcho": "true", +"MaxLength": "16", +"ConstraintDescription": "must begin with a letter and..." +}, +"LinuxDistribution": { +"Default": "F17", +"Type": "String", +"Description": "Distribution of choice", +"AllowedValues": [ +"F18", +"F17", +"U10", +"RHEL-6.1", +"RHEL-6.2", +"RHEL-6.3" +] +}, +"DBRootPassword": { +"Type": "String", +"Description": "Root password for MySQL", +"Default": "admin", +"MinLength": "1", +"AllowedPattern": "[a-zA-Z0-9]*", +"NoEcho": "true", +"MaxLength": "41", +"ConstraintDescription": "must contain only alphanumeric characters." +}, +"KeyName": { +"Type": "String", +"Description": "Name of an EC2 Key Pair to enable SSH access to the instances" +}, +"DBName": { +"Type": "String", +"Description": "The WordPress database name", +"Default": "wordpress", +"MinLength": "1", +"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", +"MaxLength": "64", +"ConstraintDescription": "must begin with a letter and..." +}, +"DBPassword": { +"Type": "String", +"Description": "The WordPress database admin account password", +"Default": "admin", +"MinLength": "1", +"AllowedPattern": "[a-zA-Z0-9]*", +"NoEcho": "true", +"MaxLength": "41", +"ConstraintDescription": "must contain only alphanumeric characters." +}, +"InstanceType": { +"Default": "m1.small", +"Type": "String", +"ConstraintDescription": "must be a valid EC2 instance type.", +"Description": "WebServer EC2 instance type", +"AllowedValues": [ +"m1.tiny", +"m1.small", +"m1.medium", +"m1.large", +"m1.xlarge" +] +}, +"Network": { +"Type": "String", +"CustomConstraint": "neutron.network" +} +} +} +""" + +ENVIRONMENT = """ +parameters: + InstanceType: m1.xsmall + db_password: verybadpass + KeyName: heat_key +""" + +SNAPSHOT_CREATE = """ +{ + "status": "IN_PROGRESS", + "name": "None", + "data": "None", + "creation_time": "2016-02-19T07:25:23.494152", + "status_reason": "None", + "id": "8af90c07-b788-44ee-a8ab-5990197f5e32" +} +""" + + +class Environment(object): + def __init__(self, data): + self.data = data + + +class Template(object): + def __init__(self, data, validate): + self.data = data + self.validate = validate + + +class Snapshot(object): + def __init__(self, data): + self.data = data + + +def data(TEST): + TEST.stacks = utils.TestDataContainer() + TEST.stack_templates = utils.TestDataContainer() + TEST.stack_environments = utils.TestDataContainer() + TEST.stack_snapshot_create = utils.TestDataContainer() + TEST.stack_snapshot = utils.TestDataContainer() + TEST.resource_types = utils.TestDataContainer() + TEST.heat_resources = utils.TestDataContainer() + TEST.heat_services = utils.TestDataContainer() + TEST.template_versions = utils.TestDataContainer() + TEST.template_functions = utils.TestDataContainer() + + # Services + service_1 = services.Service(services.ServiceManager(None), { + "status": "up", + "binary": "heat-engine", + "report_interval": 60, + "engine_id": "2f7b5a9b-c50b-4b01-8248-f89f5fb338d1", + "created_at": "2015-02-06T03:23:32.000000", + "hostname": "mrkanag", + "updated_at": "2015-02-20T09:49:52.000000", + "topic": "engine", + "host": "engine-1", + "deleted_at": None, + "id": "1efd7015-5016-4caa-b5c8-12438af7b100" + }) + + service_2 = services.Service(services.ServiceManager(None), { + "status": "up", + "binary": "heat-engine", + "report_interval": 60, + "engine_id": "2f7b5a9b-c50b-4b01-8248-f89f5fb338d2", + "created_at": "2015-02-06T03:23:32.000000", + "hostname": "mrkanag", + "updated_at": "2015-02-20T09:49:52.000000", + "topic": "engine", + "host": "engine-2", + "deleted_at": None, + "id": "1efd7015-5016-4caa-b5c8-12438af7b100" + }) + + TEST.heat_services.add(service_1) + TEST.heat_services.add(service_2) + + # Data return by heatclient. + TEST.api_resource_types = utils.TestDataContainer() + + for i in range(10): + stack_data = { + "description": "No description", + "links": [{ + "href": "http://192.168.1.70:8004/v1/" + "051c727ee67040d6a7b7812708485a97/" + "stacks/stack-test{0}/" + "05b4f39f-ea96-4d91-910c-e758c078a089{0}".format(i), + "rel": "self" + }], + "parameters": { + 'DBUsername': '******', + 'InstanceType': 'm1.small', + 'AWS::StackId': ( + 'arn:openstack:heat::2ce287:stacks/teststack/88553ec'), + 'DBRootPassword': '******', + 'AWS::StackName': "teststack{0}".format(i), + 'DBPassword': '******', + 'AWS::Region': 'ap-southeast-1', + 'DBName': u'wordpress' + }, + "stack_status_reason": "Stack successfully created", + "stack_name": "stack-test{0}".format(i), + "creation_time": "2013-04-22T00:11:39Z", + "updated_time": "2013-04-22T00:11:39Z", + "stack_status": "CREATE_COMPLETE", + "id": "05b4f39f-ea96-4d91-910c-e758c078a089{0}".format(i) + } + stack = stacks.Stack(stacks.StackManager(None), stack_data) + TEST.stacks.add(stack) + + for i in range(10): + snapshot_data = { + "status": "COMPLETE", + "name": 'null', + "data": { + "files": {}, + "status": "COMPLETE", + "name": "zhao3", + "tags": ["a", " 123", " b", " 456"], + "stack_user_project_id": "3cba4460875444049a2a7cc5420ccddb", + "environment": { + "encrypted_param_names": [], + "parameter_defaults": {}, + "event_sinks": [], + "parameters": {}, + "resource_registry": { + "resources": {} + } + }, + "template": { + "heat_template_version": "2013-05-23", + "description": + "HOT template for Test.", + "resources": { + "private_subnet": { + "type": "OS::Neutron::Subnet", + "properties": { + "network_id": {"get_resource": "private_net"}, + "cidr": "172.16.2.0/24", + "gateway_ip": "172.16.2.1" + } + }, + "private_net": { + "type": "OS::Neutron::Net", + "properties": {"name": "private-net"} + } + } + }, + "action": "SNAPSHOT", + "project_id": "1acd0026829f4d28bb2eff912d7aad0d", + "id": "70650725-bdbd-419f-b53f-5707767bfe0e", + "resources": { + "private_subnet": { + "status": "COMPLETE", + "name": "private_subnet", + "resource_data": {}, + "resource_id": "9c7211b3-31c7-41f6-b92a-442ad3f71ef0", + "action": "SNAPSHOT", + "type": "OS::Neutron::Subnet", + "metadata": {} + }, + "private_net": { + "status": "COMPLETE", + "name": "private_net", + "resource_data": {}, + "resource_id": "ff4fd287-31b2-4d00-bc96-c409bc1db027", + "action": "SNAPSHOT", + "type": "OS::Neutron::Net", + "metadata": {} + } + } + }, + + "creation_time": "2016-02-21T04:02:54", + "status_reason": "Stack SNAPSHOT completed successfully", + "id": "01558a3b-ba05-4427-bbb4-1e4ab71cfca{0}".format(i) + } + TEST.stack_snapshot.add(snapshot_data) + + TEST.stack_templates.add(Template(TEMPLATE, VALIDATE)) + TEST.stack_environments.add(Environment(ENVIRONMENT)) + TEST.stack_snapshot_create.add(Snapshot(SNAPSHOT_CREATE)) + + # Resource types list + r_type_1 = { + "resource_type": "AWS::CloudFormation::Stack", + "attributes": {}, + "properties": { + "Parameters": { + "description": + "The set of parameters passed to this nested stack.", + "immutable": False, + "required": False, + "type": "map", + "update_allowed": True}, + "TemplateURL": { + "description": "The URL of a template that specifies" + " the stack to be created as a resource.", + "immutable": False, + "required": True, + "type": "string", + "update_allowed": True}, + "TimeoutInMinutes": { + "description": "The length of time, in minutes," + " to wait for the nested stack creation.", + "immutable": False, + "required": False, + "type": "number", + "update_allowed": True} + } + } + + r_type_2 = { + "resource_type": "OS::Heat::CloudConfig", + "attributes": { + "config": { + "description": "The config value of the software config."} + }, + "properties": { + "cloud_config": { + "description": "Map representing the cloud-config data" + " structure which will be formatted as YAML.", + "immutable": False, + "required": False, + "type": "map", + "update_allowed": False} + } + } + + r_types_list = [r_type_1, r_type_2] + + for rt in r_types_list: + r_type = resource_types.ResourceType( + resource_types.ResourceTypeManager(None), rt['resource_type']) + TEST.resource_types.add(r_type) + TEST.api_resource_types.add(rt) + + # Resources + resource_1 = resources.Resource(resources.ResourceManager(None), { + "logical_resource_id": "my_resource", + "physical_resource_id": "7b5e29b1-c94d-402d-b69c-df9ac6dfc0ce", + "resource_name": "my_resource", + "links": [ + { + "href": "http://192.168.1.70:8004/v1/" + "051c727ee67040d6a7b7812708485a97/" + "stacks/%s/%s/resources/my_resource" % + (TEST.stacks.first().stack_name, + TEST.stacks.first().id), + "rel": "self" + }, + { + "href": "http://192.168.1.70:8004/v1/" + "051c727ee67040d6a7b7812708485a97/" + "stacks/%s/%s" % + (TEST.stacks.first().stack_name, + TEST.stacks.first().id), + "rel": "stack" + } + ], + "attributes": { + "metadata": {} + } + }) + + TEST.heat_resources.add(resource_1) + + # Template versions + template_version_1 = template_versions.TemplateVersion( + template_versions.TemplateVersionManager(None), { + "version": "HeatTemplateFormatVersion.2012-12-12", + "type": "cfn" + }) + + template_version_2 = template_versions.TemplateVersion( + template_versions.TemplateVersionManager(None), { + "version": "heat_template_version.2013-05-23", + "type": "hot" + }) + + TEST.template_versions.add(template_version_1) + TEST.template_versions.add(template_version_2) + + # Template functions + template_function_1 = template_versions.TemplateVersion( + template_versions.TemplateVersionManager(None), { + "functions": "Fn::GetAZs", + "description": "A function for retrieving the availability zones." + }) + + template_function_2 = template_versions.TemplateVersion( + template_versions.TemplateVersionManager(None), { + "functions": "Fn::Join", + "description": "A function for joining strings." + }) + + TEST.template_functions.add(template_function_1) + TEST.template_functions.add(template_function_2) diff --git a/heat_dashboard/test/test_data/keystone_data.py b/heat_dashboard/test/test_data/keystone_data.py new file mode 100644 index 0000000..fdef296 --- /dev/null +++ b/heat_dashboard/test/test_data/keystone_data.py @@ -0,0 +1,339 @@ +# 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. + +import copy +from datetime import timedelta + +from django.conf import settings +from django.utils import datetime_safe + +from keystoneclient import access +from keystoneclient.v2_0 import tenants +from keystoneclient.v2_0 import users +from keystoneclient.v3.contrib.federation import identity_providers +from keystoneclient.v3.contrib.federation import mappings +from keystoneclient.v3.contrib.federation import protocols +from keystoneclient.v3 import domains + +from openstack_auth import user as auth_user + +from heat_dashboard.test.test_data import utils + + +# Dummy service catalog with all service. +# All endpoint URLs should point to example.com. +# Try to keep them as accurate to real data as possible (ports, URIs, etc.) +SERVICE_CATALOG = [ + {"type": "compute", + "name": "nova", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.nova.example.com:8774/v2", + "internalURL": "http://int.nova.example.com:8774/v2", + "publicURL": "http://public.nova.example.com:8774/v2"}, + {"region": "RegionTwo", + "adminURL": "http://admin.nova2.example.com:8774/v2", + "internalURL": "http://int.nova2.example.com:8774/v2", + "publicURL": "http://public.nova2.example.com:8774/v2"}]}, + {"type": "volumev2", + "name": "cinderv2", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.nova.example.com:8776/v2", + "internalURL": "http://int.nova.example.com:8776/v2", + "publicURL": "http://public.nova.example.com:8776/v2"}, + {"region": "RegionTwo", + "adminURL": "http://admin.nova.example.com:8776/v2", + "internalURL": "http://int.nova.example.com:8776/v2", + "publicURL": "http://public.nova.example.com:8776/v2"}]}, + {"type": "image", + "name": "glance", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.glance.example.com:9292", + "internalURL": "http://int.glance.example.com:9292", + "publicURL": "http://public.glance.example.com:9292"}]}, + {"type": "identity", + "name": "keystone", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.keystone.example.com:35357/v2.0", + "internalURL": "http://int.keystone.example.com:5000/v2.0", + "publicURL": "http://public.keystone.example.com:5000/v2.0"}]}, + {"type": "object-store", + "name": "swift", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.swift.example.com:8080/", + "internalURL": "http://int.swift.example.com:8080/", + "publicURL": "http://public.swift.example.com:8080/"}]}, + {"type": "network", + "name": "neutron", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.neutron.example.com:9696/", + "internalURL": "http://int.neutron.example.com:9696/", + "publicURL": "http://public.neutron.example.com:9696/"}]}, + {"type": "ec2", + "name": "EC2 Service", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.nova.example.com:8773/services/Admin", + "publicURL": "http://public.nova.example.com:8773/services/Cloud", + "internalURL": "http://int.nova.example.com:8773/services/Cloud"}]}, + {"type": "orchestration", + "name": "Heat", + "endpoints_links": [], + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.heat.example.com:8004/v1", + "publicURL": "http://public.heat.example.com:8004/v1", + "internalURL": "http://int.heat.example.com:8004/v1"}]} +] + + +def data(TEST): + # Make a deep copy of the catalog to avoid persisting side-effects + # when tests modify the catalog. + TEST.service_catalog = copy.deepcopy(SERVICE_CATALOG) + TEST.tokens = utils.TestDataContainer() + TEST.domains = utils.TestDataContainer() + TEST.users = utils.TestDataContainer() + # TEST.groups = utils.TestDataContainer() + TEST.tenants = utils.TestDataContainer() + # TEST.role_assignments = utils.TestDataContainer() + # TEST.roles = utils.TestDataContainer() + # TEST.ec2 = utils.TestDataContainer() + + TEST.identity_providers = utils.TestDataContainer() + TEST.idp_mappings = utils.TestDataContainer() + TEST.idp_protocols = utils.TestDataContainer() + + # admin_role_dict = {'id': '1', + # 'name': 'admin'} + # admin_role = roles.Role(roles.RoleManager, admin_role_dict, loaded=True) + member_role_dict = {'id': "2", + 'name': settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE} + # member_role = roles.Role(roles.RoleManager, + # member_role_dict, loaded=True) + # TEST.roles.add(admin_role, member_role) + # TEST.roles.admin = admin_role + # TEST.roles.member = member_role + + domain_dict = {'id': "1", + 'name': 'test_domain', + 'description': "a test domain.", + 'enabled': True} + domain_dict_2 = {'id': "2", + 'name': 'disabled_domain', + 'description': "a disabled test domain.", + 'enabled': False} + domain_dict_3 = {'id': "3", + 'name': 'another_test_domain', + 'description': "another test domain.", + 'enabled': True} + domain = domains.Domain(domains.DomainManager, domain_dict) + disabled_domain = domains.Domain(domains.DomainManager, domain_dict_2) + another_domain = domains.Domain(domains.DomainManager, domain_dict_3) + TEST.domains.add(domain, disabled_domain, another_domain) + TEST.domain = domain # Your "current" domain + + user_dict = {'id': "1", + 'name': 'test_user', + 'description': 'test_description', + 'email': 'test@example.com', + 'password': 'password', + 'token': 'test_token', + 'project_id': '1', + 'enabled': True, + 'domain_id': "1"} + user = users.User(None, user_dict) + user_dict = {'id': "2", + 'name': 'user_two', + 'description': 'test_description', + 'email': 'two@example.com', + 'password': 'password', + 'token': 'test_token', + 'project_id': '1', + 'enabled': True, + 'domain_id': "1"} + user2 = users.User(None, user_dict) + user_dict = {'id': "3", + 'name': 'user_three', + 'description': 'test_description', + 'email': 'three@example.com', + 'password': 'password', + 'token': 'test_token', + 'project_id': '1', + 'enabled': True, + 'domain_id': "1"} + user3 = users.User(None, user_dict) + user_dict = {'id': "4", + 'name': 'user_four', + 'description': 'test_description', + 'email': 'four@example.com', + 'password': 'password', + 'token': 'test_token', + 'project_id': '2', + 'enabled': True, + 'domain_id': "2"} + user4 = users.User(None, user_dict) + user_dict = {'id': "5", + 'name': 'user_five', + 'description': 'test_description', + 'email': None, + 'password': 'password', + 'token': 'test_token', + 'project_id': '2', + 'enabled': True, + 'domain_id': "1"} + user5 = users.User(None, user_dict) + TEST.users.add(user, user2, user3, user4, user5) + TEST.user = user # Your "current" user + TEST.user.service_catalog = copy.deepcopy(SERVICE_CATALOG) + + tenant_dict = {'id': "1", + 'name': 'test_tenant', + 'description': "a test tenant.", + 'enabled': True, + 'domain_id': '1', + 'domain_name': 'test_domain'} + tenant_dict_2 = {'id': "2", + 'name': 'disabled_tenant', + 'description': "a disabled test tenant.", + 'enabled': False, + 'domain_id': '2', + 'domain_name': 'disabled_domain'} + tenant_dict_3 = {'id': "3", + 'name': u'\u4e91\u89c4\u5219', + 'description': "an unicode-named tenant.", + 'enabled': True, + 'domain_id': '2', + 'domain_name': 'disabled_domain'} + tenant = tenants.Tenant(tenants.TenantManager, tenant_dict) + disabled_tenant = tenants.Tenant(tenants.TenantManager, tenant_dict_2) + tenant_unicode = tenants.Tenant(tenants.TenantManager, tenant_dict_3) + + TEST.tenants.add(tenant, disabled_tenant, tenant_unicode) + TEST.tenant = tenant # Your "current" tenant + + tomorrow = datetime_safe.datetime.now() + timedelta(days=1) + expiration = tomorrow.isoformat() + + scoped_token_dict = { + 'access': { + 'token': { + 'id': "test_token_id", + 'expires': expiration, + 'tenant': tenant_dict, + 'tenants': [tenant_dict]}, + 'user': { + 'id': "test_user_id", + 'name': "test_user", + 'roles': [member_role_dict]}, + 'serviceCatalog': TEST.service_catalog + } + } + + scoped_access_info = access.AccessInfo.factory(resp=None, + body=scoped_token_dict) + + unscoped_token_dict = { + 'access': { + 'token': { + 'id': "test_token_id", + 'expires': expiration}, + 'user': { + 'id': "test_user_id", + 'name': "test_user", + 'roles': [member_role_dict]}, + 'serviceCatalog': TEST.service_catalog + } + } + unscoped_access_info = access.AccessInfo.factory(resp=None, + body=unscoped_token_dict) + + scoped_token = auth_user.Token(scoped_access_info) + unscoped_token = auth_user.Token(unscoped_access_info) + TEST.tokens.add(scoped_token, unscoped_token) + TEST.token = scoped_token # your "current" token. + TEST.tokens.scoped_token = scoped_token + TEST.tokens.unscoped_token = unscoped_token + + idp_dict_1 = {'id': 'idp_1', + 'description': 'identity provider 1', + 'enabled': True, + 'remote_ids': ['rid_1', 'rid_2']} + idp_1 = identity_providers.IdentityProvider( + identity_providers.IdentityProviderManager, + idp_dict_1, loaded=True) + idp_dict_2 = {'id': 'idp_2', + 'description': 'identity provider 2', + 'enabled': True, + 'remote_ids': ['rid_3', 'rid_4']} + idp_2 = identity_providers.IdentityProvider( + identity_providers.IdentityProviderManager, + idp_dict_2, loaded=True) + TEST.identity_providers.add(idp_1, idp_2) + + idp_mapping_dict = { + "id": "mapping_1", + "rules": [ + { + "local": [ + { + "user": { + "name": "{0}" + } + }, + { + "group": { + "id": "0cd5e9" + } + } + ], + "remote": [ + { + "type": "UserName" + }, + { + "type": "orgPersonType", + "not_any_of": [ + "Contractor", + "Guest" + ] + } + ] + } + ] + } + idp_mapping = mappings.Mapping( + mappings.MappingManager(None), + idp_mapping_dict) + TEST.idp_mappings.add(idp_mapping) + + idp_protocol_dict_1 = {'id': 'protocol_1', + 'mapping_id': 'mapping_1'} + idp_protocol = protocols.Protocol( + protocols.ProtocolManager, + idp_protocol_dict_1, + loaded=True) + TEST.idp_protocols.add(idp_protocol) diff --git a/heat_dashboard/test/test_data/neutron_data.py b/heat_dashboard/test/test_data/neutron_data.py new file mode 100644 index 0000000..837200c --- /dev/null +++ b/heat_dashboard/test/test_data/neutron_data.py @@ -0,0 +1,77 @@ +# 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. + +import copy + +from openstack_dashboard.api import neutron + +from heat_dashboard.test.test_data import utils + + +def data(TEST): + # Data returned by openstack_dashboard.api.neutron wrapper. + TEST.networks = utils.TestDataContainer() + + # Data return by neutronclient. + TEST.api_networks = utils.TestDataContainer() + TEST.api_subnets = utils.TestDataContainer() + + # 1st network. + network_dict = {'admin_state_up': True, + 'id': '82288d84-e0a5-42ac-95be-e6af08727e42', + 'name': 'net1', + 'status': 'ACTIVE', + 'subnets': ['e8abc972-eb0c-41f1-9edd-4bc6e3bcd8c9', + '41e53a49-442b-4307-9e9a-88967a6b6657'], + 'tenant_id': '1', + 'router:external': False, + 'shared': False} + subnet_dict = {'allocation_pools': [{'end': '10.0.0.254', + 'start': '10.0.0.2'}], + 'dns_nameservers': [], + 'host_routes': [], + 'cidr': '10.0.0.0/24', + 'enable_dhcp': True, + 'gateway_ip': '10.0.0.1', + 'id': network_dict['subnets'][0], + 'ip_version': 4, + 'name': 'mysubnet1', + 'network_id': network_dict['id'], + 'tenant_id': network_dict['tenant_id']} + subnetv6_dict = { + 'allocation_pools': [{'start': 'fdb6:b88a:488e::2', + 'end': 'fdb6:b88a:488e:0:ffff:ffff:ffff:ffff'}], + 'dns_nameservers': [], + 'host_routes': [], + 'cidr': 'fdb6:b88a:488e::/64', + 'enable_dhcp': True, + 'gateway_ip': 'fdb6:b88a:488e::1', + 'id': network_dict['subnets'][1], + 'ip_version': 6, + 'name': 'myv6subnet', + 'network_id': network_dict['id'], + 'tenant_id': network_dict['tenant_id'], + 'ipv6_ra_mode': 'slaac', + 'ipv6_address_mode': 'slaac' + } + + TEST.api_networks.add(network_dict) + TEST.api_subnets.add(subnet_dict) + TEST.api_subnets.add(subnetv6_dict) + + network = copy.deepcopy(network_dict) + subnet = neutron.Subnet(subnet_dict) + subnetv6 = neutron.Subnet(subnetv6_dict) + network['subnets'] = [subnet, subnetv6] + TEST.networks.add(neutron.Network(network)) diff --git a/heat_dashboard/test/test_data/utils.py b/heat_dashboard/test/test_data/utils.py new file mode 100644 index 0000000..b34aaf6 --- /dev/null +++ b/heat_dashboard/test/test_data/utils.py @@ -0,0 +1,128 @@ +# 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. + + +def load_test_data(load_onto=None): + from heat_dashboard.test.test_data import exceptions + from heat_dashboard.test.test_data import heat_data + from heat_dashboard.test.test_data import keystone_data + from heat_dashboard.test.test_data import neutron_data + + # The order of these loaders matters, some depend on others. + loaders = ( + exceptions.data, + keystone_data.data, + # glance_data.data, + # nova_data.data, + # cinder_data.data, + neutron_data.data, + # swift_data.data, + heat_data.data, + ) + if load_onto: + for data_func in loaders: + data_func(load_onto) + return load_onto + else: + return TestData(*loaders) + + +class TestData(object): + """Holder object for test data. + + Any functions passed to the init method will be called with the + ``TestData`` object as their only argument. + They can then load data onto the object as desired. + + The idea is to use the instantiated object like this:: + + >>> import glance_data + >>> TEST = TestData(glance_data.data) + >>> TEST.images.list() + [, ] + >>> TEST.images.first() + + + You can load as little or as much data as you like as long as the loaders + don't conflict with each other. + + See the + :class:`~openstack_dashboard.test.test_data.utils.TestDataContainer` + class for a list of available methods. + """ + def __init__(self, *args): + for data_func in args: + data_func(self) + + +class TestDataContainer(object): + """A container for test data objects. + + The behavior of this class is meant to mimic a "manager" class, which + has convenient shortcuts for common actions like "list", "filter", "get", + and "add". + """ + def __init__(self): + self._objects = [] + + def add(self, *args): + """Add a new object to this container. + + Generally this method should only be used during data loading, since + adding data during a test can affect the results of other tests. + """ + for obj in args: + if obj not in self._objects: + self._objects.append(obj) + + def list(self): + """Returns a list of all objects in this container.""" + return self._objects + + def filter(self, filtered=None, **kwargs): + """Returns objects whose attributes match the given kwargs.""" + if filtered is None: + filtered = self._objects + try: + key, value = kwargs.popitem() + except KeyError: + # We're out of filters, return + return filtered + + def get_match(obj): + return hasattr(obj, key) and getattr(obj, key) == value + + filtered = [obj for obj in filtered if get_match(obj)] + return self.filter(filtered=filtered, **kwargs) + + def get(self, **kwargs): + """Returns a single object whose attributes match the given kwargs. + + An error will be raised if the arguments + provided don't return exactly one match. + """ + matches = self.filter(**kwargs) + if not matches: + raise Exception("No matches found.") + elif len(matches) > 1: + raise Exception("Multiple matches found.") + else: + return matches.pop() + + def first(self): + """Returns the first object from this container.""" + return self._objects[0] + + def count(self): + return len(self._objects) diff --git a/heat_dashboard/test/tests/api/heat_rest_tests.py b/heat_dashboard/test/tests/api/heat_rest_tests.py index b8e4607..d20e405 100644 --- a/heat_dashboard/test/tests/api/heat_rest_tests.py +++ b/heat_dashboard/test/tests/api/heat_rest_tests.py @@ -15,8 +15,6 @@ import json import mock -# from openstack_dashboard.api.rest import heat -# from openstack_dashboard.test import helpers as test from heat_dashboard.api.rest import heat from heat_dashboard.test import helpers as test from openstack_dashboard import api diff --git a/heat_dashboard/test/tests/api/heat_tests.py b/heat_dashboard/test/tests/api/heat_tests.py index c58fe83..590ee8c 100644 --- a/heat_dashboard/test/tests/api/heat_tests.py +++ b/heat_dashboard/test/tests/api/heat_tests.py @@ -14,8 +14,6 @@ import six from django.conf import settings from django.test.utils import override_settings -# from openstack_dashboard import api -# from openstack_dashboard.test import helpers as test from heat_dashboard import api from heat_dashboard.test import helpers as test diff --git a/heat_dashboard/test/tests/content/test_resource_types.py b/heat_dashboard/test/tests/content/test_resource_types.py index 3022355..b73129f 100644 --- a/heat_dashboard/test/tests/content/test_resource_types.py +++ b/heat_dashboard/test/tests/content/test_resource_types.py @@ -16,7 +16,6 @@ from django import http from mox3.mox import IsA -# from openstack_dashboard import api from heat_dashboard import api from heat_dashboard.test import helpers as test @@ -32,7 +31,7 @@ class ResourceTypesTests(test.TestCase): self.mox.ReplayAll() res = self.client.get( - reverse('horizon:project:stacks.resource_types:index')) + reverse('horizon:project:resource_types:index')) self.assertTemplateUsed( res, 'horizon/common/_data_table_view.html') self.assertContains(res, 'AWS::CloudFormation::Stack') @@ -45,7 +44,7 @@ class ResourceTypesTests(test.TestCase): IsA(http.HttpRequest), rt['resource_type']).AndReturn(rt) self.mox.ReplayAll() - url = reverse('horizon:project:stacks.resource_types:details', + url = reverse('horizon:project:resource_types:details', args=[rt['resource_type']]) res = self.client.get(url) diff --git a/heat_dashboard/test/tests/content/test_stacks.py b/heat_dashboard/test/tests/content/test_stacks.py index 0d3d84e..fd51285 100644 --- a/heat_dashboard/test/tests/content/test_stacks.py +++ b/heat_dashboard/test/tests/content/test_stacks.py @@ -30,10 +30,6 @@ from heat_dashboard import api from heat_dashboard.test import helpers as test from openstack_dashboard import api as dashboard_api -# from openstack_dashboard.dashboards.project.stacks import api as project_api -# from openstack_dashboard.dashboards.project.stacks import forms -# from openstack_dashboard.dashboards.project.stacks import mappings -# from openstack_dashboard.dashboards.project.stacks import tables from heat_dashboard.content.stacks import forms from heat_dashboard.content.stacks import mappings from heat_dashboard.content.stacks import tables diff --git a/heat_dashboard/test/tests/content/test_template_versions.py b/heat_dashboard/test/tests/content/test_template_versions.py index cd31184..fbc6da5 100644 --- a/heat_dashboard/test/tests/content/test_template_versions.py +++ b/heat_dashboard/test/tests/content/test_template_versions.py @@ -16,14 +16,13 @@ from django import http from mox3.mox import IsA -# from openstack_dashboard import api -# from openstack_dashboard.test import helpers as test from heat_dashboard import api from heat_dashboard.test import helpers as test class TemplateVersionsTests(test.TestCase): - INDEX_URL = reverse('horizon:project:stacks.template_versions:index') + + INDEX_URL = reverse('horizon:project:template_versions:index') @test.create_stubs({api.heat: ('template_version_list',)}) def test_index(self): @@ -33,7 +32,7 @@ class TemplateVersionsTests(test.TestCase): res = self.client.get(self.INDEX_URL) self.assertTemplateUsed( - res, 'project/stacks.template_versions/index.html') + res, 'project/template_versions/index.html') self.assertContains(res, 'HeatTemplateFormatVersion.2012-12-12') @test.create_stubs({api.heat: ('template_version_list',)}) @@ -44,7 +43,7 @@ class TemplateVersionsTests(test.TestCase): res = self.client.get(self.INDEX_URL) self.assertTemplateUsed( - res, 'project/stacks.template_versions/index.html') + res, 'project/template_versions/index.html') self.assertEqual(len(res.context['table'].data), 0) self.assertMessageCount(res, error=1) @@ -57,7 +56,7 @@ class TemplateVersionsTests(test.TestCase): IsA(http.HttpRequest), t_version).AndReturn(t_functions) self.mox.ReplayAll() - url = reverse('horizon:project:stacks.template_versions:details', + url = reverse('horizon:project:template_versions:details', args=[t_version]) res = self.client.get(url) @@ -69,10 +68,11 @@ class TemplateVersionsTests(test.TestCase): t_version = self.template_versions.first().version api.heat.template_function_list( - IsA(http.HttpRequest), t_version).AndRaise(self.exceptions.heat) + IsA(http.HttpRequest), t_version).\ + AndRaise(self.exceptions.heat) self.mox.ReplayAll() - url = reverse('horizon:project:stacks.template_versions:details', + url = reverse('horizon:project:template_versions:details', args=[t_version]) res = self.client.get(url)