summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Borland <matt.borland@hpe.com>2016-07-07 10:32:43 -0600
committerMatt Borland <matt.borland@hpe.com>2016-08-05 08:17:21 -0600
commit20bc6e1516f0cb5d4c9dbec464dc823cd8ed6b44 (patch)
treed6b053a8e6e70c80003d0a7531798fffa2511dad
parentde1a23267aa5ad60702282917b53c1913c85e566 (diff)
Make 'switch' between legacy and Angular Images
This patch follows on the example that the Containers set, providing a 'switch' in the panel-enablement file that currently defaults to 'legacy' (Python-based Images panel) and allows for 'angular' (Angular- based Images panel). To be clear, this does NOT enable Angular Images. It's just setting the stage to do so at some point, or to allow deployers/devs to easily switch between the two. A switch both for HORIZON_CONFIG and for integration tests is necessary due to the way integration tests operate. Co-Authored-By: Timur Sufiev <tsufiev@mirantis.com> Change-Id: I12cd33552218ed1082d2d9a2ae8982639a217a6a Partially-Implements: blueprint angularize-images-table
Notes
Notes (review): Code-Review+2: Travis Tripp <travis.tripp@hpe.com> Code-Review+1: Ying Zuo <yinzuo@cisco.com> Code-Review+2: Rob Cresswell <robert.cresswell@outlook.com> Workflow+1: Rob Cresswell <robert.cresswell@outlook.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Mon, 08 Aug 2016 13:11:25 +0000 Reviewed-on: https://review.openstack.org/339122 Project: openstack/horizon Branch: refs/heads/master
-rwxr-xr-xdoc/source/topics/settings.rst12
-rw-r--r--openstack_dashboard/dashboards/admin/images/urls.py26
-rw-r--r--openstack_dashboard/dashboards/admin/images/views.py5
-rw-r--r--openstack_dashboard/dashboards/project/images/images/urls.py21
-rw-r--r--openstack_dashboard/dashboards/project/images/urls.py19
-rw-r--r--openstack_dashboard/dashboards/project/images/views.py5
-rw-r--r--openstack_dashboard/dashboards/project/instances/tests.py5
-rw-r--r--openstack_dashboard/dashboards/project/ngimages/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/ngimages/panel.py23
-rw-r--r--openstack_dashboard/dashboards/project/ngimages/urls.py22
-rw-r--r--openstack_dashboard/dashboards/project/ngimages/views.py19
-rw-r--r--openstack_dashboard/enabled/_1050_project_images_panel.py14
-rw-r--r--openstack_dashboard/enabled/_1051_project_ng_images_panel.py30
-rw-r--r--openstack_dashboard/settings.py1
-rw-r--r--openstack_dashboard/static/app/core/images/images.module.js18
-rw-r--r--openstack_dashboard/static/app/core/images/summary.controller.js (renamed from openstack_dashboard/static/app/core/images/details/drawer.controller.js)0
-rw-r--r--openstack_dashboard/static/app/core/images/summary.controller.spec.js (renamed from openstack_dashboard/static/app/core/images/details/drawer.controller.spec.js)0
-rw-r--r--openstack_dashboard/test/integration_tests/config.py3
-rw-r--r--openstack_dashboard/test/integration_tests/decorators.py57
-rw-r--r--openstack_dashboard/test/integration_tests/horizon.conf1
-rw-r--r--openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py11
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_images.py41
-rw-r--r--openstack_dashboard/test/settings.py1
-rw-r--r--releasenotes/notes/image-panel-switch-38e9d3716451f9e3.yaml12
24 files changed, 199 insertions, 147 deletions
diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst
index 9cbb635..460a403 100755
--- a/doc/source/topics/settings.rst
+++ b/doc/source/topics/settings.rst
@@ -180,6 +180,18 @@ A dictionary containing classes of exceptions which Horizon's centralized
180exception handling should be aware of. Based on these exception categories, 180exception handling should be aware of. Based on these exception categories,
181Horizon will handle the exception and display a message to the user. 181Horizon will handle the exception and display a message to the user.
182 182
183``images_panel``
184-----------
185
186.. versionadded:: 10.0.0(Newton)
187
188Default: ``legacy``
189
190There are currently two panel types that may be specified: ``legacy`` and
191``angular``. ``legacy`` will display the Python-based (server-side) Images
192panel and ``angular`` will display the Angular-based (client-side) Images
193panel.
194
183``modal_backdrop`` 195``modal_backdrop``
184------------------ 196------------------
185 197
diff --git a/openstack_dashboard/dashboards/admin/images/urls.py b/openstack_dashboard/dashboards/admin/images/urls.py
index 2a907c9..d19b970 100644
--- a/openstack_dashboard/dashboards/admin/images/urls.py
+++ b/openstack_dashboard/dashboards/admin/images/urls.py
@@ -16,16 +16,24 @@
16# License for the specific language governing permissions and limitations 16# License for the specific language governing permissions and limitations
17# under the License. 17# under the License.
18 18
19from django.conf import settings
19from django.conf.urls import url 20from django.conf.urls import url
20 21
21from openstack_dashboard.dashboards.admin.images import views 22from openstack_dashboard.dashboards.admin.images import views
22 23
23 24if settings.HORIZON_CONFIG['images_panel'] == 'angular':
24urlpatterns = [ 25 # New angular images
25 url(r'^$', views.IndexView.as_view(), name='index'), 26 urlpatterns = [
26 url(r'^create/$', views.CreateView.as_view(), name='create'), 27 url(r'^$', views.AngularIndexView.as_view(), name='index'),
27 url(r'^(?P<image_id>[^/]+)/update/$', 28 url(r'^(?P<image_id>[^/]+)/detail/$',
28 views.UpdateView.as_view(), name='update'), 29 views.AngularIndexView.as_view(), name='detail'),
29 url(r'^(?P<image_id>[^/]+)/detail/$', 30 ]
30 views.DetailView.as_view(), name='detail') 31else:
31] 32 urlpatterns = [
33 url(r'^$', views.IndexView.as_view(), name='index'),
34 url(r'^create/$', views.CreateView.as_view(), name='create'),
35 url(r'^(?P<image_id>[^/]+)/update/$',
36 views.UpdateView.as_view(), name='update'),
37 url(r'^(?P<image_id>[^/]+)/detail/$',
38 views.DetailView.as_view(), name='detail')
39 ]
diff --git a/openstack_dashboard/dashboards/admin/images/views.py b/openstack_dashboard/dashboards/admin/images/views.py
index 35a0dfc..554e776 100644
--- a/openstack_dashboard/dashboards/admin/images/views.py
+++ b/openstack_dashboard/dashboards/admin/images/views.py
@@ -23,6 +23,7 @@ from oslo_utils import units
23from django.core.urlresolvers import reverse 23from django.core.urlresolvers import reverse
24from django.core.urlresolvers import reverse_lazy 24from django.core.urlresolvers import reverse_lazy
25from django.utils.translation import ugettext_lazy as _ 25from django.utils.translation import ugettext_lazy as _
26from django.views import generic
26 27
27from horizon import exceptions 28from horizon import exceptions
28from horizon import messages 29from horizon import messages
@@ -40,6 +41,10 @@ from openstack_dashboard.dashboards.admin.images \
40LOG = logging.getLogger(__name__) 41LOG = logging.getLogger(__name__)
41 42
42 43
44class AngularIndexView(generic.TemplateView):
45 template_name = 'angular.html'
46
47
43class IndexView(tables.DataTableView): 48class IndexView(tables.DataTableView):
44 DEFAULT_FILTERS = {'is_public': None} 49 DEFAULT_FILTERS = {'is_public': None}
45 table_class = project_tables.AdminImagesTable 50 table_class = project_tables.AdminImagesTable
diff --git a/openstack_dashboard/dashboards/project/images/images/urls.py b/openstack_dashboard/dashboards/project/images/images/urls.py
index 7e63484b..8d01d1a 100644
--- a/openstack_dashboard/dashboards/project/images/images/urls.py
+++ b/openstack_dashboard/dashboards/project/images/images/urls.py
@@ -16,14 +16,23 @@
16# License for the specific language governing permissions and limitations 16# License for the specific language governing permissions and limitations
17# under the License. 17# under the License.
18 18
19from django.conf import settings
19from django.conf.urls import url 20from django.conf.urls import url
20 21
21from openstack_dashboard.dashboards.project.images.images import views 22from openstack_dashboard.dashboards.project.images.images import views
23from openstack_dashboard.dashboards.project.images import views as imgviews
22 24
23 25
24urlpatterns = [ 26if settings.HORIZON_CONFIG['images_panel'] == 'angular':
25 url(r'^create/$', views.CreateView.as_view(), name='create'), 27 urlpatterns = [
26 url(r'^(?P<image_id>[^/]+)/update/$', 28 url(r'^(?P<image_id>[^/]+)/$', imgviews.AngularIndexView.as_view(),
27 views.UpdateView.as_view(), name='update'), 29 name='detail'),
28 url(r'^(?P<image_id>[^/]+)/$', views.DetailView.as_view(), name='detail'), 30 ]
29] 31else:
32 urlpatterns = [
33 url(r'^create/$', views.CreateView.as_view(), name='create'),
34 url(r'^(?P<image_id>[^/]+)/update/$',
35 views.UpdateView.as_view(), name='update'),
36 url(r'^(?P<image_id>[^/]+)/$', views.DetailView.as_view(),
37 name='detail'),
38 ]
diff --git a/openstack_dashboard/dashboards/project/images/urls.py b/openstack_dashboard/dashboards/project/images/urls.py
index 71ded71..fd9e574 100644
--- a/openstack_dashboard/dashboards/project/images/urls.py
+++ b/openstack_dashboard/dashboards/project/images/urls.py
@@ -16,6 +16,7 @@
16# License for the specific language governing permissions and limitations 16# License for the specific language governing permissions and limitations
17# under the License. 17# under the License.
18 18
19from django.conf import settings
19from django.conf.urls import include 20from django.conf.urls import include
20from django.conf.urls import url 21from django.conf.urls import url
21 22
@@ -26,8 +27,16 @@ from openstack_dashboard.dashboards.project.images.snapshots \
26from openstack_dashboard.dashboards.project.images import views 27from openstack_dashboard.dashboards.project.images import views
27 28
28 29
29urlpatterns = [ 30if settings.HORIZON_CONFIG['images_panel'] == 'angular':
30 url(r'^$', views.IndexView.as_view(), name='index'), 31 # New angular images
31 url(r'', include(image_urls, namespace='images')), 32 urlpatterns = [
32 url(r'', include(snapshot_urls, namespace='snapshots')), 33 url(r'^$', views.AngularIndexView.as_view(), name='index'),
33] 34 url(r'', include(image_urls, namespace='images')),
35 url(r'', include(snapshot_urls, namespace='snapshots')),
36 ]
37else:
38 urlpatterns = [
39 url(r'^$', views.IndexView.as_view(), name='index'),
40 url(r'', include(image_urls, namespace='images')),
41 url(r'', include(snapshot_urls, namespace='snapshots')),
42 ]
diff --git a/openstack_dashboard/dashboards/project/images/views.py b/openstack_dashboard/dashboards/project/images/views.py
index 621f6d9..7ed0764 100644
--- a/openstack_dashboard/dashboards/project/images/views.py
+++ b/openstack_dashboard/dashboards/project/images/views.py
@@ -22,6 +22,7 @@ Views for managing Images and Snapshots.
22""" 22"""
23 23
24from django.utils.translation import ugettext_lazy as _ 24from django.utils.translation import ugettext_lazy as _
25from django.views import generic
25 26
26from horizon import exceptions 27from horizon import exceptions
27from horizon import messages 28from horizon import messages
@@ -34,6 +35,10 @@ from openstack_dashboard.dashboards.project.images.images \
34 import tables as images_tables 35 import tables as images_tables
35 36
36 37
38class AngularIndexView(generic.TemplateView):
39 template_name = 'angular.html'
40
41
37class IndexView(tables.DataTableView): 42class IndexView(tables.DataTableView):
38 table_class = images_tables.ImagesTable 43 table_class = images_tables.ImagesTable
39 template_name = 'project/images/index.html' 44 template_name = 'project/images/index.html'
diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py
index b22c862..ca089fa 100644
--- a/openstack_dashboard/dashboards/project/instances/tests.py
+++ b/openstack_dashboard/dashboards/project/instances/tests.py
@@ -1276,11 +1276,6 @@ class InstanceTests(helpers.TestCase):
1276 server.id, 1276 server.id,
1277 "snapshot1").AndReturn(self.snapshots.first()) 1277 "snapshot1").AndReturn(self.snapshots.first())
1278 1278
1279 api.glance.image_list_detailed(IsA(http.HttpRequest),
1280 marker=None,
1281 paginate=True) \
1282 .AndReturn([[], False, False])
1283
1284 self.mox.ReplayAll() 1279 self.mox.ReplayAll()
1285 1280
1286 formData = {'instance_id': server.id, 1281 formData = {'instance_id': server.id,
diff --git a/openstack_dashboard/dashboards/project/ngimages/__init__.py b/openstack_dashboard/dashboards/project/ngimages/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/openstack_dashboard/dashboards/project/ngimages/__init__.py
+++ /dev/null
diff --git a/openstack_dashboard/dashboards/project/ngimages/panel.py b/openstack_dashboard/dashboards/project/ngimages/panel.py
deleted file mode 100644
index 93e1833..0000000
--- a/openstack_dashboard/dashboards/project/ngimages/panel.py
+++ /dev/null
@@ -1,23 +0,0 @@
1# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from django.utils.translation import ugettext_lazy as _
16
17import horizon
18
19
20class NGImages(horizon.Panel):
21 name = _("Images")
22 slug = 'ngimages'
23 permissions = ('openstack.services.image',)
diff --git a/openstack_dashboard/dashboards/project/ngimages/urls.py b/openstack_dashboard/dashboards/project/ngimages/urls.py
deleted file mode 100644
index 82ed696..0000000
--- a/openstack_dashboard/dashboards/project/ngimages/urls.py
+++ /dev/null
@@ -1,22 +0,0 @@
1# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from django.conf.urls import url
16
17from openstack_dashboard.dashboards.project.ngimages import views
18
19
20urlpatterns = [
21 url('', views.IndexView.as_view(), name='index'),
22]
diff --git a/openstack_dashboard/dashboards/project/ngimages/views.py b/openstack_dashboard/dashboards/project/ngimages/views.py
deleted file mode 100644
index e072aea..0000000
--- a/openstack_dashboard/dashboards/project/ngimages/views.py
+++ /dev/null
@@ -1,19 +0,0 @@
1# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from django.views import generic
16
17
18class IndexView(generic.TemplateView):
19 template_name = 'angular.html'
diff --git a/openstack_dashboard/enabled/_1050_project_images_panel.py b/openstack_dashboard/enabled/_1050_project_images_panel.py
index 5033577..88e1a82 100644
--- a/openstack_dashboard/enabled/_1050_project_images_panel.py
+++ b/openstack_dashboard/enabled/_1050_project_images_panel.py
@@ -1,3 +1,17 @@
1# (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
1# The slug of the panel to be added to HORIZON_CONFIG. Required. 15# The slug of the panel to be added to HORIZON_CONFIG. Required.
2PANEL = 'images' 16PANEL = 'images'
3# The slug of the dashboard the PANEL associated with. Required. 17# The slug of the dashboard the PANEL associated with. Required.
diff --git a/openstack_dashboard/enabled/_1051_project_ng_images_panel.py b/openstack_dashboard/enabled/_1051_project_ng_images_panel.py
deleted file mode 100644
index a92ec15..0000000
--- a/openstack_dashboard/enabled/_1051_project_ng_images_panel.py
+++ /dev/null
@@ -1,30 +0,0 @@
1# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15# The slug of the dashboard the PANEL associated with. Required.
16PANEL_DASHBOARD = 'project'
17
18# The slug of the panel group the PANEL is associated with.
19# If you want the panel to show up without a panel group,
20# use the panel group "default".
21PANEL_GROUP = 'compute'
22
23# The slug of the panel to be added to HORIZON_CONFIG. Required.
24PANEL = 'ngimages'
25
26# If set to True, this settings file will not be added to the settings.
27DISABLED = True
28
29# Python panel class of the PANEL to be added.
30ADD_PANEL = 'openstack_dashboard.dashboards.project.ngimages.panel.NGImages'
diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
index 682e5f8..64dbab2 100644
--- a/openstack_dashboard/settings.py
+++ b/openstack_dashboard/settings.py
@@ -79,6 +79,7 @@ HORIZON_CONFIG = {
79 'js_spec_files': [], 79 'js_spec_files': [],
80 'external_templates': [], 80 'external_templates': [],
81 'plugins': [], 81 'plugins': [],
82 'images_panel': 'legacy',
82 'integration_tests_support': INTEGRATION_TESTS_SUPPORT 83 'integration_tests_support': INTEGRATION_TESTS_SUPPORT
83} 84}
84 85
diff --git a/openstack_dashboard/static/app/core/images/images.module.js b/openstack_dashboard/static/app/core/images/images.module.js
index 113809d..2aea051 100644
--- a/openstack_dashboard/static/app/core/images/images.module.js
+++ b/openstack_dashboard/static/app/core/images/images.module.js
@@ -295,9 +295,25 @@
295 var path = $windowProvider.$get().STATIC_URL + 'app/core/images/'; 295 var path = $windowProvider.$get().STATIC_URL + 'app/core/images/';
296 $provide.constant('horizon.app.core.images.basePath', path); 296 $provide.constant('horizon.app.core.images.basePath', path);
297 297
298 $routeProvider.when('/project/ngimages/', { 298 $routeProvider.when('/project/images/:id/', {
299 redirectTo: goToAngularDetails
300 });
301
302 $routeProvider.when('/admin/images/:id/detail/', {
303 redirectTo: goToAngularDetails
304 });
305
306 $routeProvider.when('/project/images/', {
299 templateUrl: path + 'panel.html' 307 templateUrl: path + 'panel.html'
300 }); 308 });
309
310 $routeProvider.when('/admin/images/', {
311 templateUrl: path + 'panel.html'
312 });
313
314 function goToAngularDetails(params) {
315 return 'project/ngdetails/OS::Glance::Image/' + params.id;
316 }
301 } 317 }
302 318
303})(); 319})();
diff --git a/openstack_dashboard/static/app/core/images/details/drawer.controller.js b/openstack_dashboard/static/app/core/images/summary.controller.js
index 05b2fc1..05b2fc1 100644
--- a/openstack_dashboard/static/app/core/images/details/drawer.controller.js
+++ b/openstack_dashboard/static/app/core/images/summary.controller.js
diff --git a/openstack_dashboard/static/app/core/images/details/drawer.controller.spec.js b/openstack_dashboard/static/app/core/images/summary.controller.spec.js
index 38b765f..38b765f 100644
--- a/openstack_dashboard/static/app/core/images/details/drawer.controller.spec.js
+++ b/openstack_dashboard/static/app/core/images/summary.controller.spec.js
diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py
index bd95cde..2bcb6ab 100644
--- a/openstack_dashboard/test/integration_tests/config.py
+++ b/openstack_dashboard/test/integration_tests/config.py
@@ -57,6 +57,9 @@ IdentityGroup = [
57] 57]
58 58
59ImageGroup = [ 59ImageGroup = [
60 cfg.StrOpt('panel_type',
61 default='legacy',
62 help='type/version of images panel'),
60 cfg.StrOpt('http_image', 63 cfg.StrOpt('http_image',
61 default='http://download.cirros-cloud.net/0.3.1/' 64 default='http://download.cirros-cloud.net/0.3.1/'
62 'cirros-0.3.1-x86_64-uec.tar.gz', 65 'cirros-0.3.1-x86_64-uec.tar.gz',
diff --git a/openstack_dashboard/test/integration_tests/decorators.py b/openstack_dashboard/test/integration_tests/decorators.py
index 14c1a16..a787aeb 100644
--- a/openstack_dashboard/test/integration_tests/decorators.py
+++ b/openstack_dashboard/test/integration_tests/decorators.py
@@ -58,6 +58,18 @@ NOT_TEST_OBJECT_ERROR_MSG = "Decorator can be applied only on test" \
58 " classes and test methods." 58 " classes and test methods."
59 59
60 60
61def _get_skip_method(obj):
62 """Make sure that we can decorate both methods and classes."""
63 if inspect.isclass(obj):
64 if not _is_test_cls(obj):
65 raise ValueError(NOT_TEST_OBJECT_ERROR_MSG)
66 return _mark_class_skipped
67 else:
68 if not _is_test_method_name(obj.__name__):
69 raise ValueError(NOT_TEST_OBJECT_ERROR_MSG)
70 return _mark_method_skipped
71
72
61def services_required(*req_services): 73def services_required(*req_services):
62 """Decorator for marking test's service requirements, 74 """Decorator for marking test's service requirements,
63 if requirements are not met in the configuration file 75 if requirements are not met in the configuration file
@@ -85,15 +97,7 @@ def services_required(*req_services):
85 . 97 .
86 """ 98 """
87 def actual_decoration(obj): 99 def actual_decoration(obj):
88 # make sure that we can decorate method and classes as well 100 skip_method = _get_skip_method(obj)
89 if inspect.isclass(obj):
90 if not _is_test_cls(obj):
91 raise ValueError(NOT_TEST_OBJECT_ERROR_MSG)
92 skip_method = _mark_class_skipped
93 else:
94 if not _is_test_method_name(obj.__name__):
95 raise ValueError(NOT_TEST_OBJECT_ERROR_MSG)
96 skip_method = _mark_method_skipped
97 # get available services from configuration 101 # get available services from configuration
98 avail_services = config.get_config().service_available 102 avail_services = config.get_config().service_available
99 for req_service in req_services: 103 for req_service in req_services:
@@ -105,6 +109,32 @@ def services_required(*req_services):
105 return actual_decoration 109 return actual_decoration
106 110
107 111
112def _parse_compound_config_option_value(option_name):
113 """Parses the value of a given config option where option's section name is
114 separated from option name by '.'.
115 """
116 name_parts = option_name.split('.')
117 name_parts.reverse()
118 option = config.get_config()
119 while name_parts:
120 option = getattr(option, name_parts.pop())
121 return option
122
123
124def config_option_required(option_key, required_value, message=None):
125 if message is None:
126 message = "%s option equal to '%s' is required for this test to work" \
127 " properly." % (option_key, required_value)
128
129 def actual_decoration(obj):
130 skip_method = _get_skip_method(obj)
131 option_value = _parse_compound_config_option_value(option_key)
132 if option_value != required_value:
133 obj = skip_method(obj, message)
134 return obj
135 return actual_decoration
136
137
108def skip_because(**kwargs): 138def skip_because(**kwargs):
109 """Decorator for skipping tests hitting known bugs 139 """Decorator for skipping tests hitting known bugs
110 140
@@ -120,14 +150,7 @@ def skip_because(**kwargs):
120 . 150 .
121 """ 151 """
122 def actual_decoration(obj): 152 def actual_decoration(obj):
123 if inspect.isclass(obj): 153 skip_method = _get_skip_method(obj)
124 if not _is_test_cls(obj):
125 raise ValueError(NOT_TEST_OBJECT_ERROR_MSG)
126 skip_method = _mark_class_skipped
127 else:
128 if not _is_test_method_name(obj.__name__):
129 raise ValueError(NOT_TEST_OBJECT_ERROR_MSG)
130 skip_method = _mark_method_skipped
131 bugs = kwargs.get("bugs") 154 bugs = kwargs.get("bugs")
132 if bugs and isinstance(bugs, collections.Iterable): 155 if bugs and isinstance(bugs, collections.Iterable):
133 for bug in bugs: 156 for bug in bugs:
diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf
index 31ccc70..b225dff 100644
--- a/openstack_dashboard/test/integration_tests/horizon.conf
+++ b/openstack_dashboard/test/integration_tests/horizon.conf
@@ -35,6 +35,7 @@ maximize_browser=yes
35 35
36[image] 36[image]
37# http accessible image (string value) 37# http accessible image (string value)
38panel_type=legacy
38http_image=http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz 39http_image=http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz
39images_list=cirros-0.3.4-x86_64-uec, 40images_list=cirros-0.3.4-x86_64-uec,
40 cirros-0.3.4-x86_64-uec-kernel, 41 cirros-0.3.4-x86_64-uec-kernel,
diff --git a/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py b/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py
index 42117d4..2bb7b83 100644
--- a/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py
+++ b/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py
@@ -9,6 +9,8 @@
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations 10# License for the specific language governing permissions and limitations
11# under the License. 11# under the License.
12from selenium.webdriver.common import by
13
12from openstack_dashboard.test.integration_tests.pages import basepage 14from openstack_dashboard.test.integration_tests.pages import basepage
13from openstack_dashboard.test.integration_tests.regions import forms 15from openstack_dashboard.test.integration_tests.regions import forms
14from openstack_dashboard.test.integration_tests.regions import tables 16from openstack_dashboard.test.integration_tests.regions import tables
@@ -250,3 +252,12 @@ class ImagesPage(basepage.BaseNavigationPage):
250 launch_instance.count.value = instance_count 252 launch_instance.count.value = instance_count
251 launch_instance.submit() 253 launch_instance.submit()
252 return InstancesPage(self.driver, self.conf) 254 return InstancesPage(self.driver, self.conf)
255
256
257class ImagesPageNG(ImagesPage):
258 _resource_page_header_locator = (by.By.CSS_SELECTOR,
259 'hz-resource-panel hz-page-header h1')
260
261 @property
262 def header(self):
263 return self._get_element(*self._resource_page_header_locator)
diff --git a/openstack_dashboard/test/integration_tests/tests/test_images.py b/openstack_dashboard/test/integration_tests/tests/test_images.py
index ec264d4..9e40ca2 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_images.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_images.py
@@ -15,14 +15,39 @@ from openstack_dashboard.test.integration_tests import helpers
15from openstack_dashboard.test.integration_tests.regions import messages 15from openstack_dashboard.test.integration_tests.regions import messages
16 16
17 17
18class TestImagesBasic(helpers.TestCase): 18@decorators.config_option_required('image.panel_type', 'legacy',
19 """Login as demo user""" 19 message="Angular Panels not tested")
20 IMAGE_NAME = helpers.gen_random_resource_name("image") 20class TestImagesLegacy(helpers.TestCase):
21
22 @property 21 @property
23 def images_page(self): 22 def images_page(self):
24 return self.home_pg.go_to_compute_imagespage() 23 return self.home_pg.go_to_compute_imagespage()
25 24
25
26@decorators.config_option_required('image.panel_type', 'angular',
27 message="Legacy Panels not tested")
28class TestImagesAngular(helpers.TestCase):
29 @property
30 def images_page(self):
31 # FIXME(tsufiev): had to return angularized version of Images Page
32 # object with the horrendous hack below because it's not so easy to
33 # wire into the Navigation machinery and tell it to return an '*NG'
34 # version of ImagesPage class if one adds '_ng' suffix to
35 # 'go_to_compute_imagespage()' method. Yet that's how it should work
36 # (or rewrite Navigation module completely).
37 from openstack_dashboard.test.integration_tests.pages.project.\
38 compute.imagespage import ImagesPageNG
39 self.home_pg.go_to_compute_imagespage()
40 return ImagesPageNG(self.driver, self.CONFIG)
41
42 def test_basic_image_browse(self):
43 images_page = self.images_page
44 self.assertEqual(images_page.header.text, 'Images')
45
46
47class TestImagesBasic(TestImagesLegacy):
48 """Login as demo user"""
49 IMAGE_NAME = helpers.gen_random_resource_name("image")
50
26 def image_create(self, local_file=None): 51 def image_create(self, local_file=None):
27 images_page = self.images_page 52 images_page = self.images_page
28 if local_file: 53 if local_file:
@@ -227,14 +252,10 @@ class TestImagesBasic(helpers.TestCase):
227 self.image_delete(new_image_name) 252 self.image_delete(new_image_name)
228 253
229 254
230class TestImagesAdvanced(helpers.TestCase): 255class TestImagesAdvanced(TestImagesLegacy):
231 """Login as demo user""" 256 """Login as demo user"""
232 IMAGE_NAME = helpers.gen_random_resource_name("image") 257 IMAGE_NAME = helpers.gen_random_resource_name("image")
233 258
234 @property
235 def images_page(self):
236 return self.home_pg.go_to_compute_imagespage()
237
238 def test_create_volume_from_image(self): 259 def test_create_volume_from_image(self):
239 """This test case checks create volume from image functionality: 260 """This test case checks create volume from image functionality:
240 Steps: 261 Steps:
@@ -293,7 +314,7 @@ class TestImagesAdvanced(helpers.TestCase):
293 self.assertTrue(instances_page.is_instance_deleted(target_instance)) 314 self.assertTrue(instances_page.is_instance_deleted(target_instance))
294 315
295 316
296class TestImagesAdmin(helpers.AdminTestCase, TestImagesBasic): 317class TestImagesAdmin(helpers.AdminTestCase, TestImagesLegacy):
297 """Login as admin user""" 318 """Login as admin user"""
298 IMAGE_NAME = helpers.gen_random_resource_name("image") 319 IMAGE_NAME = helpers.gen_random_resource_name("image")
299 320
diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py
index fdd0a4a..cecabc2 100644
--- a/openstack_dashboard/test/settings.py
+++ b/openstack_dashboard/test/settings.py
@@ -96,6 +96,7 @@ HORIZON_CONFIG = {
96 'unauthorized': exceptions.UNAUTHORIZED}, 96 'unauthorized': exceptions.UNAUTHORIZED},
97 'angular_modules': [], 97 'angular_modules': [],
98 'js_files': [], 98 'js_files': [],
99 'images_panel': 'legacy',
99} 100}
100 101
101# Load the pluggable dashboard settings 102# Load the pluggable dashboard settings
diff --git a/releasenotes/notes/image-panel-switch-38e9d3716451f9e3.yaml b/releasenotes/notes/image-panel-switch-38e9d3716451f9e3.yaml
new file mode 100644
index 0000000..a52a50e
--- /dev/null
+++ b/releasenotes/notes/image-panel-switch-38e9d3716451f9e3.yaml
@@ -0,0 +1,12 @@
1---
2prelude: >
3 The Image panel now may be configured to use
4 either the legacy or Angular code.
5features:
6 - HORIZON_CONFIG now allows for a key 'images_panel' to be
7 specified as 'legacy' or 'angular' indicating the type of
8 panel to use.
9 - Integration tests for Image features may also be toggled
10 in openstack_dashboard/test/integration_tests/horizon.conf
11 using the 'panel_type' feature, either set to 'legacy' or
12 'angular' to match the enabled panel type.