From abad2d3af4fcf1c19703dcd76ff0e1439dafe182 Mon Sep 17 00:00:00 2001 From: Serhii Vasheka Date: Mon, 19 Oct 2015 18:29:06 +0300 Subject: [PATCH] Added tests for check volumes management functionality Tests check that create/edit/delete volumes actions are executed without errors under admin and non-admin user. Volumes page object was defined similar to other pages. 'volume' section is added to horizon.conf Implements blueprint: horizon-integration-tests-coverage Change-Id: Ib2c2bcb98bd010232fea404e565591cf6140383f --- .../test/integration_tests/basewebobject.py | 4 + .../test/integration_tests/config.py | 10 ++ .../test/integration_tests/horizon.conf | 4 + .../pages/admin/system/volumes/__init__.py | 0 .../pages/admin/system/volumes/volumespage.py | 18 +++ .../pages/project/compute/imagespage.py | 3 - .../project/compute/volumes/volumespage.py | 137 ++++++++++++++++++ .../integration_tests/tests/test_volumes.py | 115 +++++++++++++++ 8 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 openstack_dashboard/test/integration_tests/pages/admin/system/volumes/__init__.py create mode 100644 openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumespage.py create mode 100644 openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumespage.py create mode 100644 openstack_dashboard/test/integration_tests/tests/test_volumes.py diff --git a/openstack_dashboard/test/integration_tests/basewebobject.py b/openstack_dashboard/test/integration_tests/basewebobject.py index 93ad221501..8436e9b34d 100644 --- a/openstack_dashboard/test/integration_tests/basewebobject.py +++ b/openstack_dashboard/test/integration_tests/basewebobject.py @@ -95,6 +95,10 @@ class BaseWebObject(unittest.TestCase): actually waiting for a _different_ element with a different text to appear in place of an old element. So a way to avoid capturing stale element reference should be provided for this use case. + + Better to wrap getting entity status cell in a lambda + to avoid problems with cell being replaced with totally different + element by Javascript """ def predicate(_): elt = element() if hasattr(element, '__call__') else element diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py index 477735bd28..399d15f3d2 100644 --- a/openstack_dashboard/test/integration_tests/config.py +++ b/openstack_dashboard/test/integration_tests/config.py @@ -79,6 +79,15 @@ InstancesGroup = [ help="Boot Source to be selected for launch Instances"), ] +VolumeGroup = [ + cfg.StrOpt('volume_type', + default='lvmdriver-1', + help='Default volume type'), + cfg.StrOpt('volume_size', + default='1', + help='Default volume size ') +] + PluginGroup = [ cfg.BoolOpt('is_plugin', default='False', @@ -110,5 +119,6 @@ def get_config(): cfg.CONF.register_opts(ScenarioGroup, group="scenario") cfg.CONF.register_opts(InstancesGroup, group="launch_instances") cfg.CONF.register_opts(PluginGroup, group="plugin") + cfg.CONF.register_opts(VolumeGroup, group="volume") return cfg.CONF diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf index d34c7dc727..ffb5fc8e61 100644 --- a/openstack_dashboard/test/integration_tests/horizon.conf +++ b/openstack_dashboard/test/integration_tests/horizon.conf @@ -56,3 +56,7 @@ ssh_user=cirros available_zone=nova #image_name to launch instances image_name=cirros-0.3.4-x86_64-uec (24.0 MB) + +[volume] +volume_type=lvmdriver-1 +volume_size=1 diff --git a/openstack_dashboard/test/integration_tests/pages/admin/system/volumes/__init__.py b/openstack_dashboard/test/integration_tests/pages/admin/system/volumes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumespage.py b/openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumespage.py new file mode 100644 index 0000000000..ddcffaf4a7 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumespage.py @@ -0,0 +1,18 @@ +# 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 openstack_dashboard.test.integration_tests.pages.project.compute.volumes\ + import volumespage + + +class VolumesPage(volumespage.VolumesPage): + pass 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 2298ae87c9..80a7882df3 100644 --- a/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py +++ b/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py @@ -96,9 +96,6 @@ class ImagesPage(basepage.BaseNavigationPage): def is_image_active(self, name): row = self._get_row_with_image_name(name) - # NOTE(tsufiev): better to wrap getting image status cell in a lambda - # to avoid problems with cell being replaced with totally different - # element by Javascript def cell_getter(): return row.cells[self.IMAGES_TABLE_STATUS_COLUMN] try: diff --git a/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumespage.py b/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumespage.py new file mode 100644 index 0000000000..4d2f24760b --- /dev/null +++ b/openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumespage.py @@ -0,0 +1,137 @@ +# 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 selenium.common import exceptions + +from openstack_dashboard.test.integration_tests.pages import basepage +from openstack_dashboard.test.integration_tests.regions import forms +from openstack_dashboard.test.integration_tests.regions import tables + +VOLUME_SOURCE_TYPE = 'Volume' +IMAGE_SOURCE_TYPE = 'Image' + + +class VolumesTable(tables.TableRegion): + name = 'volumes' + + # This form is applicable for volume creation from image only. + # Volume creation from volume requires additional 'volume_source' field + # which is available only in case at least one volume is already present. + CREATE_VOLUME_FROM_IMAGE_FORM_FIELDS = ( + "name", "description", "volume_source_type", "image_source", + "type", "size", "availability_zone") + + EDIT_VOLUME_FORM_FIELDS = ("name", "description") + + @tables.bind_table_action('create') + def create_volume(self, create_button): + create_button.click() + return forms.FormRegion( + self.driver, self.conf, + field_mappings=self.CREATE_VOLUME_FROM_IMAGE_FORM_FIELDS) + + @tables.bind_table_action('delete') + def delete_volume(self, delete_button): + delete_button.click() + return forms.BaseFormRegion(self.driver, self.conf) + + @tables.bind_row_action('edit', primary=True) + def edit_volume(self, edit_button, row): + edit_button.click() + return forms.FormRegion(self.driver, self.conf, + field_mappings=self.EDIT_VOLUME_FORM_FIELDS) + + +class VolumesPage(basepage.BaseNavigationPage): + + VOLUMES_TABLE_NAME_COLUMN = 'name' + VOLUMES_TABLE_STATUS_COLUMN = 'status' + + def __init__(self, driver, conf): + super(VolumesPage, self).__init__(driver, conf) + self._page_title = "Volumes" + + def _get_row_with_volume_name(self, name): + return self.volumes_table.get_row( + self.VOLUMES_TABLE_NAME_COLUMN, name) + + @property + def volumes_table(self): + return VolumesTable(self.driver, self.conf) + + def create_volume(self, volume_name, description=None, + volume_source_type=IMAGE_SOURCE_TYPE, + volume_size=None, + volume_source=None): + volume_form = self.volumes_table.create_volume() + volume_form.name.text = volume_name + if description is not None: + volume_form.description.text = description + volume_form.volume_source_type.text = volume_source_type + volume_source_type = self._get_source_name(volume_form, + volume_source_type, + self.conf.launch_instances, + volume_source) + volume_source_type[0].text = volume_source_type[1] + if volume_size is None: + volume_size = self.conf.volume.volume_size + volume_form.size.value = volume_size + if volume_source_type != "Volume": + volume_form.type.value = self.conf.volume.volume_type + volume_form.availability_zone.value = \ + self.conf.launch_instances.available_zone + volume_form.submit() + + def delete_volume(self, name): + row = self._get_row_with_volume_name(name) + row.mark() + confirm_delete_volumes_form = self.volumes_table.delete_volume() + confirm_delete_volumes_form.submit() + + def edit_volume(self, name, new_name=None, description=None): + row = self._get_row_with_volume_name(name) + volume_edit_form = self.volumes_table.edit_volume(row) + if new_name: + volume_edit_form.name.text = new_name + if description: + volume_edit_form.description.text = description + volume_edit_form.submit() + + def is_volume_present(self, name): + return bool(self._get_row_with_volume_name(name)) + + def is_volume_status(self, name, status): + row = self._get_row_with_volume_name(name) + + def cell_getter(): + return row.cells[self.VOLUMES_TABLE_STATUS_COLUMN] + + try: + self._wait_till_text_present_in_element(cell_getter, status) + except exceptions.TimeoutException: + return False + return True + + def is_volume_deleted(self, name): + try: + getter = lambda: self._get_row_with_volume_name(name) + self.wait_till_element_disappears(getter) + except exceptions.TimeoutException: + return False + return True + + def _get_source_name(self, volume_form, volume_source_type, conf, + volume_source): + if volume_source_type == IMAGE_SOURCE_TYPE: + return volume_form.image_source, conf.image_name + if volume_source_type == VOLUME_SOURCE_TYPE: + return volume_form.volume_id, volume_source diff --git a/openstack_dashboard/test/integration_tests/tests/test_volumes.py b/openstack_dashboard/test/integration_tests/tests/test_volumes.py new file mode 100644 index 0000000000..3dda57e58d --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test_volumes.py @@ -0,0 +1,115 @@ +# 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 openstack_dashboard.test.integration_tests import helpers +from openstack_dashboard.test.integration_tests.regions import messages + + +class TestVolumes(helpers.TestCase): + VOLUME_NAME = helpers.gen_random_resource_name("volume") + + def test_volume_create_edit_delete(self): + """This test case checks create, edit, delete volume functionality + executed by non-admin user:: + Steps: + 1. Login to Horizon Dashboard as horizon user + 2. Navigate to Project -> Compute -> Volumes page + 3. Create new volume + 4. Check that the volume is in the list + 5. Check that no Error messages present + 6. Edit the volume + 7. Check that the volume is still in the list + 8. Check that no Error messages present + 9. Delete the volume + 10. Check that the volume is absent in the list + 11. Check that no Error messages present + """ + volumes_page = self.home_pg.go_to_compute_volumes_volumespage() + + volumes_page.create_volume(self.VOLUME_NAME) + self.assertTrue( + volumes_page.find_message_and_dismiss(messages.INFO)) + self.assertFalse( + volumes_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(volumes_page.is_volume_present(self.VOLUME_NAME)) + self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME, + 'Available')) + + new_name = "edited_" + self.VOLUME_NAME + volumes_page.edit_volume(self.VOLUME_NAME, new_name, "description") + self.VOLUME_NAME = new_name + self.assertTrue( + volumes_page.find_message_and_dismiss(messages.INFO)) + self.assertFalse( + volumes_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(volumes_page.is_volume_present(self.VOLUME_NAME)) + self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME, + 'Available')) + + volumes_page.delete_volume(self.VOLUME_NAME) + self.assertTrue( + volumes_page.find_message_and_dismiss(messages.SUCCESS)) + self.assertFalse( + volumes_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(volumes_page.is_volume_deleted(self.VOLUME_NAME)) + + +class TestAdminVolumes(helpers.AdminTestCase): + VOLUME_NAME = helpers.gen_random_resource_name("volume") + + def test_volume_create_edit_delete_through_admin(self): + """This test case checks create, edit, delete volume functionality + executed by admin user: + Steps: + 1. Login to Horizon Dashboard as admin user + 2. Navigate to Project -> Compute -> Volumes page + 3. Create new volume + 4. Check that the volume is in the list + 5. Check that no Error messages present + 6. Edit the volume + 7. Check that the volume is still in the list + 8. Check that no Error messages present + 9. Go to Admin/System/Volumes page + 10. Delete the volume + 11. Check that the volume is absent in the list + 12. Check that no Error messages present + """ + volumes_page = self.home_pg.go_to_compute_volumes_volumespage() + + volumes_page.create_volume(self.VOLUME_NAME) + self.assertTrue( + volumes_page.find_message_and_dismiss(messages.INFO)) + self.assertFalse( + volumes_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(volumes_page.is_volume_present(self.VOLUME_NAME)) + self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME, + 'Available')) + + new_name = "edited_" + self.VOLUME_NAME + volumes_page.edit_volume(self.VOLUME_NAME, new_name, "description") + self.VOLUME_NAME = new_name + self.assertTrue( + volumes_page.find_message_and_dismiss(messages.INFO)) + self.assertFalse( + volumes_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(volumes_page.is_volume_present(self.VOLUME_NAME)) + self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME, + 'Available')) + + volumes_admin_page = self.home_pg.go_to_system_volumes_volumespage() + + volumes_admin_page.delete_volume(self.VOLUME_NAME) + self.assertTrue( + volumes_admin_page.find_message_and_dismiss(messages.SUCCESS)) + self.assertFalse( + volumes_admin_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(volumes_admin_page.is_volume_deleted(self.VOLUME_NAME))