diff --git a/run_tests.sh b/run_tests.sh index e3bd7773..57487337 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -383,9 +383,11 @@ function run_integration_tests { export SELENIUM_HEADLESS=1 fi + export HORIZON_INTEGRATION_TESTS_CONFIG_FILE="sahara_dashboard/test/integration_tests/horizon.conf" + echo "Running Horizon integration tests..." if [ -z "$testargs" ]; then - ${command_wrapper} nosetests openstack_dashboard/test/integration_tests/tests + ${command_wrapper} nosetests sahara_dashboard/test/integration_tests/tests else ${command_wrapper} nosetests $testargs fi diff --git a/sahara_dashboard/content/data_processing/job_binaries/tables.py b/sahara_dashboard/content/data_processing/job_binaries/tables.py index 94baa6ba..797347a7 100644 --- a/sahara_dashboard/content/data_processing/job_binaries/tables.py +++ b/sahara_dashboard/content/data_processing/job_binaries/tables.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) class CreateJobBinary(tables.LinkAction): - name = "create job binary" + name = "create_job_binary" verbose_name = _("Create Job Binary") url = "horizon:project:data_processing.job_binaries:create-job-binary" classes = ("ajax-modal",) @@ -71,14 +71,14 @@ class DeleteJobBinary(tables.DeleteAction): class DownloadJobBinary(tables.LinkAction): - name = "download job binary" + name = "download_job_binary" verbose_name = _("Download Job Binary") url = "horizon:project:data_processing.job_binaries:download" classes = ("btn-edit",) class EditJobBinary(tables.LinkAction): - name = "edit job binary" + name = "edit_job_binary" verbose_name = _("Edit Job Binary") url = "horizon:project:data_processing.job_binaries:edit-job-binary" classes = ("btn-edit", "ajax-modal",) diff --git a/sahara_dashboard/test/integration_tests/__init__.py b/sahara_dashboard/test/integration_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/test/integration_tests/horizon.conf b/sahara_dashboard/test/integration_tests/horizon.conf new file mode 100644 index 00000000..8c7ea781 --- /dev/null +++ b/sahara_dashboard/test/integration_tests/horizon.conf @@ -0,0 +1,86 @@ +# +# Configuration filed based on Tempest's tempest.conf.sample +# + +[dashboard] +# Where the dashboard can be found (string value) +dashboard_url=http://localhost/dashboard/ + +# Login page for the dashboard (string value) +login_url=http://localhost/dashboard/auth/login/ + +# Dashboard help page url (string value) +help_url=http://docs.openstack.org/ + +[selenium] +# Timeout in seconds to wait for a page to become available +# (integer value) +page_timeout=30 + +# Output directory for screenshots. +# (string value) +screenshots_directory=integration_tests_screenshots + +# Implicit timeout to wait until element become available, +# this timeout is used for every find_element, find_elements call. +# (integer value) +implicit_wait=10 + +# Explicit timeout is used for long lasting operations, +# methods using explicit timeout are usually prefixed with 'wait', +# those methods ignore implicit_wait when looking up web elements. +# (integer value) +explicit_wait=300 + +[image] +# http accessible image (string value) +http_image=http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz + +[identity] +# Username to use for non-admin API requests. (string value) +username=demo + +# API key to use when authenticating. (string value) +password=pass + +# Administrative Username to use for admin API requests. +# (string value) +admin_username=admin + +# API key to use when authenticating as admin. (string value) +admin_password=pass + +[scenario] +# ssh username for image file (string value) +ssh_user=cirros + +[launch_instances] +#available zone to launch instances +available_zone=nova +#image_name to launch instances +image_name=cirros-0.3.4-x86_64-uec (24.0 MB) + +[plugin] + +is_plugin=True +plugin_page_path=sahara_dashboard.test.integration_tests.pages +plugin_page_structure={ + "Project": + { + "Data Processing": + { + "_": + [ + "Clusters", + "Cluster Templates", + "Node Group Templates", + "Job Executions", + "Jobs", + "Job Binaries", + "Data Sources", + "Image Registry", + "Plugins" + ] + } + } + } diff --git a/sahara_dashboard/test/integration_tests/pages/__init__.py b/sahara_dashboard/test/integration_tests/pages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/test/integration_tests/pages/project/__init__.py b/sahara_dashboard/test/integration_tests/pages/project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/test/integration_tests/pages/project/data_processing/__init__.py b/sahara_dashboard/test/integration_tests/pages/project/data_processing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/test/integration_tests/pages/project/data_processing/imageregistrypage.py b/sahara_dashboard/test/integration_tests/pages/project/data_processing/imageregistrypage.py new file mode 100644 index 00000000..b50db2e2 --- /dev/null +++ b/sahara_dashboard/test/integration_tests/pages/project/data_processing/imageregistrypage.py @@ -0,0 +1,83 @@ +# 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.webdriver.common import by + +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 + + +class ImageregistryPage(basepage.BaseNavigationPage): + + _image_table_locator = (by.By.CSS_SELECTOR, 'table#image_registry') + _unregister_form_locator = (by.By.CSS_SELECTOR, 'div.modal-dialog') + _register_form_locator = (by.By.CSS_SELECTOR, 'div.modal-dialog') + + IMAGE_TABLE_NAME = "image_registry" + IMAGE_TABLE_ACTIONS = ("register_image", "unregister_images") + IMAGE_TABLE_ROW_ACTIONS = { + tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_tags", + tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ("unregister_image",) + } + TABLE_IMAGE_COLUMN = 0 + + REGISTER_FORM_IMAGE = "image" + REGISTER_FORM_USER_NAME = "user_name" + REGISTER_FORM_DESCRIPTION = "description" + REGISTER_FORM_FIELDS = (REGISTER_FORM_IMAGE, REGISTER_FORM_USER_NAME, + REGISTER_FORM_DESCRIPTION) + + def __init__(self, driver, conf): + super(ImageregistryPage, self).__init__(driver, conf) + self._page_title = "Data Processing" + + def _get_row_with_image_name(self, name): + return self.image_table.get_row(self.TABLE_IMAGE_COLUMN, name) + + @property + def image_table(self): + src_elem = self._get_element(*self._image_table_locator) + return tables.ComplexActionTableRegion(self.driver, self.conf, + src_elem, + self.IMAGE_TABLE_NAME, + self.IMAGE_TABLE_ACTIONS, + self.IMAGE_TABLE_ROW_ACTIONS) + + @property + def unregister_form(self): + src_elem = self._get_element(*self._unregister_form_locator) + return forms.BaseFormRegion(self.driver, self.conf, src_elem) + + @property + def register_form(self): + src_elem = self._get_element(*self._register_form_locator) + return forms.FormRegion(self.driver, self.conf, src_elem, + self.REGISTER_FORM_FIELDS) + + def is_image_registered(self, name): + return bool(self._get_row_with_image_name(name)) + + def unregister_image(self, name): + self._get_row_with_image_name(name).mark() + self.image_table.unregister_images.click() + self.unregister_form.submit.click() + + def register_image(self, image, user_name, description): + self.image_table.register_image.click() + self.register_form.image.text = image + self.register_form.user_name.text = user_name + self.register_form.description.text = description + self.register_form.submit.click() + + def wait_until_image_registered(self, name): + self._wait_until(lambda x: self.is_image_registered(name)) diff --git a/sahara_dashboard/test/integration_tests/pages/project/data_processing/jobbinariespage.py b/sahara_dashboard/test/integration_tests/pages/project/data_processing/jobbinariespage.py new file mode 100644 index 00000000..39392b21 --- /dev/null +++ b/sahara_dashboard/test/integration_tests/pages/project/data_processing/jobbinariespage.py @@ -0,0 +1,111 @@ +# 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 selenium.webdriver.common import by + +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 + + +class JobbinariesPage(basepage.BaseNavigationPage): + + _job_binaries_table_locator = (by.By.CSS_SELECTOR, 'table#job_binaries') + _create_job_binary_form_locator = (by.By.CSS_SELECTOR, 'div.modal-dialog') + _confirm_job_binary_deletion_form =\ + (by.By.CSS_SELECTOR, 'div.modal-dialog') + + JOB_BINARIES_TABLE_NAME = "job_binaries" + JOB_BINARIES_TABLE_ACTIONS = ("create_job_binary", "delete_job_binaries") + JOB_BINARIES_ROW_ACTIONS = { + tables.ComplexActionRowRegion.PRIMARY_ACTION: "delete_job_binary", + tables.ComplexActionRowRegion.SECONDARY_ACTIONS: + ("download_job_binary",) + } + + BINARY_NAME = "name" + BINARY_STORAGE_TYPE = "storage_type" + BINARY_URL = "url" + INTERNAL_BINARY = "internal_binary" + BINARY_PATH = "upload_file" + SCRIPT_NAME = "script_name" + SCRIPT_TEXT = "script_text" + USERNAME = "username" + PASSWORD = "password" + DESCRIPTION = "description" + + CREATE_BINARY_FORM_FIELDS = ( + BINARY_NAME, + BINARY_STORAGE_TYPE, + BINARY_URL, + INTERNAL_BINARY, + BINARY_PATH, + SCRIPT_NAME, + SCRIPT_TEXT, + USERNAME, + PASSWORD, + DESCRIPTION + ) + + # index of name column in binary jobs table + JOB_BINARIES_TABLE_NAME_COLUMN = 0 + + # fields that are set via text setter + _TEXT_FIELDS = (BINARY_NAME, BINARY_STORAGE_TYPE, INTERNAL_BINARY) + + def __init__(self, driver, conf): + super(JobbinariesPage, self).__init__(driver, conf) + self._page_title = "Data Processing" + + def _get_row_with_job_binary_name(self, name): + return self.job_binaries_table.get_row( + self.JOB_BINARIES_TABLE_NAME_COLUMN, name) + + @property + def job_binaries_table(self): + src_elem = self._get_element(*self._job_binaries_table_locator) + return tables.ComplexActionTableRegion(self.driver, + self.conf, src_elem, + self.JOB_BINARIES_TABLE_NAME, + self.JOB_BINARIES_TABLE_ACTIONS, + self.JOB_BINARIES_ROW_ACTIONS + ) + + @property + def create_job_binary_form(self): + src_elem = self._get_element(*self._create_job_binary_form_locator) + return forms.FormRegion(self.driver, self.conf, src_elem, + self.CREATE_BINARY_FORM_FIELDS) + + @property + def confirm_delete_job_binaries_form(self): + src_elem = self._get_element(*self._confirm_job_binary_deletion_form) + return forms.BaseFormRegion(self.driver, self.conf, src_elem) + + def delete_job_binary(self, name): + row = self._get_row_with_job_binary_name(name) + row.mark() + self.job_binaries_table.delete_job_binaries.click() + self.confirm_delete_job_binaries_form.submit.click() + + def create_job_binary(self, name, storage_type, url, internal_binary, + upload_file, script_name, script_text, username, + password, description): + self.job_binaries_table.create_job_binary.click() + job_data = copy.copy(locals()) + del job_data["self"] + self.create_job_binary_form.set_field_values(job_data) + self.create_job_binary_form.submit.click() + + def is_job_binary_present(self, name): + return bool(self._get_row_with_job_binary_name(name)) diff --git a/sahara_dashboard/test/integration_tests/tests/__init__.py b/sahara_dashboard/test/integration_tests/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/test/integration_tests/tests/test_sahara_image_registry.py b/sahara_dashboard/test/integration_tests/tests/test_sahara_image_registry.py new file mode 100644 index 00000000..29bbf6fd --- /dev/null +++ b/sahara_dashboard/test/integration_tests/tests/test_sahara_image_registry.py @@ -0,0 +1,45 @@ +# 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 + + +IMAGE_NAME = helpers.gen_random_resource_name("image") + + +class TestSaharaImageRegistry(helpers.TestCase): + + def setUp(self): + super(TestSaharaImageRegistry, self).setUp() + image_pg = self.home_pg.go_to_compute_imagespage() + image_pg.create_image(IMAGE_NAME) + image_pg.wait_until_image_active(IMAGE_NAME) + + def test_image_register_unregister(self): + """Test the image registration in Sahara.""" + image_reg_pg = self.home_pg.go_to_dataprocessing_imageregistrypage() + image_reg_pg.register_image(IMAGE_NAME, self.CONFIG.scenario.ssh_user, + "Test description") + image_reg_pg.wait_until_image_registered(IMAGE_NAME) + self.assertTrue(image_reg_pg.is_image_registered(IMAGE_NAME), + "Image was not registered.") + self.assertFalse(image_reg_pg.is_error_message_present(), + "Error message occurred during image creation.") + image_reg_pg.unregister_image(IMAGE_NAME) + self.assertFalse(image_reg_pg.is_error_message_present()) + self.assertFalse(image_reg_pg.is_image_registered(IMAGE_NAME), + "Image was not unregistered.") + + def tearDown(self): + image_pg = self.home_pg.go_to_compute_imagespage() + image_pg.delete_image(IMAGE_NAME) + super(TestSaharaImageRegistry, self).tearDown() diff --git a/sahara_dashboard/test/integration_tests/tests/test_sahara_job_binaries.py b/sahara_dashboard/test/integration_tests/tests/test_sahara_job_binaries.py new file mode 100644 index 00000000..41c608a6 --- /dev/null +++ b/sahara_dashboard/test/integration_tests/tests/test_sahara_job_binaries.py @@ -0,0 +1,73 @@ +# 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 collections + +from openstack_dashboard.test.integration_tests import helpers +from sahara_dashboard.test.integration_tests.pages.project.data_processing \ + import jobbinariespage + + +# NOTE(tsufiev): beware, ordering matters here because Sahara UI shows fields +# based on value of previous fields and for Selenium to be able to fill the +# field it's crucial that it's visible +JOB_BINARY_INTERNAL = collections.OrderedDict([ + # Size of binary name is limited to 50 characters + (jobbinariespage.JobbinariesPage.BINARY_NAME, + helpers.gen_random_resource_name(resource='jobbinary', + timestamp=False)[0:50]), + (jobbinariespage.JobbinariesPage.BINARY_STORAGE_TYPE, + "Internal database"), + (jobbinariespage.JobbinariesPage.INTERNAL_BINARY, "*Create a script"), + (jobbinariespage.JobbinariesPage.BINARY_URL, None), + (jobbinariespage.JobbinariesPage.BINARY_PATH, None), + (jobbinariespage.JobbinariesPage.SCRIPT_NAME, + helpers.gen_random_resource_name(resource='scriptname', timestamp=False)), + (jobbinariespage.JobbinariesPage.SCRIPT_TEXT, "test_script_text"), + (jobbinariespage.JobbinariesPage.USERNAME, None), + (jobbinariespage.JobbinariesPage.PASSWORD, None), + (jobbinariespage.JobbinariesPage.DESCRIPTION, "test description") +]) + + +class TestSaharaJobBinary(helpers.TestCase): + + def _sahara_create_delete_job_binary(self, job_binary_template): + job_name = \ + job_binary_template[jobbinariespage.JobbinariesPage.BINARY_NAME] + + # create job binary + job_binary_pg = self.home_pg.go_to_dataprocessing_jobbinariespage() + self.assertFalse(job_binary_pg.is_job_binary_present(job_name), + "Job binary was present in the binaries table" + " before its creation.") + job_binary_pg.create_job_binary(job_binary_template) + + # verify that job is created without problems + self.assertFalse(job_binary_pg.is_error_message_present(), + "Error message occurred during binary job creation.") + self.assertTrue(job_binary_pg.is_job_binary_present(job_name), + "Job binary is not in the binaries job table after" + " its creation.") + + # delete binary job + job_binary_pg.delete_job_binary(job_name) + + # verify that job was successfully deleted + self.assertFalse(job_binary_pg.is_error_message_present(), + "Error message occurred during binary job deletion.") + self.assertFalse(job_binary_pg.is_job_binary_present(job_name), + "Job binary was not removed from binaries job table.") + + def test_sahara_create_delete_job_binary_internaldb(self): + """Test the creation of a Job Binary in the Internal DB.""" + self._sahara_create_delete_job_binary(JOB_BINARY_INTERNAL) diff --git a/sahara_dashboard/test/test_data/utils.py b/sahara_dashboard/test/test_data/utils.py index e92e61af..7de46501 100644 --- a/sahara_dashboard/test/test_data/utils.py +++ b/sahara_dashboard/test/test_data/utils.py @@ -24,7 +24,6 @@ def load_test_data(load_onto=None): from openstack_dashboard.test.test_data import neutron_data from openstack_dashboard.test.test_data import nova_data from openstack_dashboard.test.test_data import swift_data - from openstack_dashboard.test.test_data import trove_data from sahara_dashboard.test.test_data import keystone_data \ as sahara_keystone_data @@ -41,7 +40,6 @@ def load_test_data(load_onto=None): swift_data.data, heat_data.data, ceilometer_data.data, - trove_data.data, sahara_data.data, sahara_keystone_data.data, ) diff --git a/test-requirements.txt b/test-requirements.txt index fd5be0b5..f5b21ef4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,8 +13,11 @@ mock>=1.2 mox3>=0.7.0 netifaces>=0.10.4 python-subunit>=0.0.18 +selenium sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 testrepository>=0.0.18 testscenarios>=0.4 testtools>=1.4.0 +# This also needs xvfb library installed on your OS +xvfbwrapper>=0.1.3 #license: MIT diff --git a/tox.ini b/tox.ini index 1234f8f8..ca2a7ba2 100644 --- a/tox.ini +++ b/tox.ini @@ -32,6 +32,10 @@ basepython = python2.7 commands = pip install django>=1.8,<1.9 /bin/bash run_tests.sh -N --no-pep8 {posargs} +[testenv:py27integration] +basepython = python2.7 +commands = /bin/bash run_tests.sh -N --integration --selenium-headless {posargs} + [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}'