From eda58de3f669abb0991bdaad791158e420615ccb Mon Sep 17 00:00:00 2001 From: TatyanaGladysheva Date: Tue, 12 Jan 2016 15:37:45 +0300 Subject: [PATCH] Added test for check stacks creation and deletion functionality Test checks that create/delete stacks actions are executed without errors under admin user. Stacks page object was defined similar to other pages. Few other modifications I've made: * horizon.conf - service_available section with new heat option * stack template is added as separate file Depends-On: I1f5dc1220aee39103289a579583095346cce0354 Implements blueprint: horizon-integration-tests-coverage Change-Id: Ibc549f9ae4eac17d8e92d65afe1c5cee9be6e72e --- .../test/integration_tests/config.py | 2 + .../test/integration_tests/horizon.conf | 2 + .../access_and_security/keypairspage.py | 11 +++ .../pages/project/orchestration/stackspage.py | 97 +++++++++++++++++++ .../tests/test-data/stack_template | 11 +++ .../integration_tests/tests/test_stacks.py | 70 +++++++++++++ tools/gate/integration/devstack_exports.sh | 6 -- tools/gate/integration/devstack_gate_rc | 4 + 8 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 openstack_dashboard/test/integration_tests/pages/project/orchestration/stackspage.py create mode 100644 openstack_dashboard/test/integration_tests/tests/test-data/stack_template create mode 100644 openstack_dashboard/test/integration_tests/tests/test_stacks.py delete mode 100644 tools/gate/integration/devstack_exports.sh create mode 100644 tools/gate/integration/devstack_gate_rc diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py index c4acee32bb..c5cfc2f9f0 100644 --- a/openstack_dashboard/test/integration_tests/config.py +++ b/openstack_dashboard/test/integration_tests/config.py @@ -70,6 +70,8 @@ NetworkGroup = [ AvailableServiceGroup = [ cfg.BoolOpt('neutron', default=True), + cfg.BoolOpt('heat', + default=True), ] SeleniumGroup = [ diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf index 27073816d8..a57028098d 100644 --- a/openstack_dashboard/test/integration_tests/horizon.conf +++ b/openstack_dashboard/test/integration_tests/horizon.conf @@ -67,6 +67,8 @@ tenant_network_cidr=10.100.0.0/16 [service_available] # Whether is Neutron expected to be available (boolean value) neutron=True +# Whether is Heat expected to be available (boolean value) +heat=True [scenario] # ssh username for image file (string value) diff --git a/openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py b/openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py index 9c2435887f..2c3a93e8c0 100644 --- a/openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py +++ b/openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py @@ -34,6 +34,11 @@ class KeypairsTable(tables.TableRegion): delete_button.click() return forms.BaseFormRegion(self.driver, self.conf) + @tables.bind_table_action('delete') + def delete_keypairs(self, delete_button): + delete_button.click() + return forms.BaseFormRegion(self.driver, self.conf) + class KeypairsPage(basepage.BaseNavigationPage): @@ -69,3 +74,9 @@ class KeypairsPage(basepage.BaseNavigationPage): row = self._get_row_with_keypair_name(name) delete_keypair_form = self.keypairs_table.delete_keypair(row) delete_keypair_form.submit() + + def delete_keypairs(self, name): + row = self._get_row_with_keypair_name(name) + row.mark() + delete_keypair_form = self.keypairs_table.delete_keypairs() + delete_keypair_form.submit() diff --git a/openstack_dashboard/test/integration_tests/pages/project/orchestration/stackspage.py b/openstack_dashboard/test/integration_tests/pages/project/orchestration/stackspage.py new file mode 100644 index 0000000000..642a0c7b51 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/pages/project/orchestration/stackspage.py @@ -0,0 +1,97 @@ +# 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 config +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 StacksTable(tables.TableRegion): + name = "stacks" + SELECT_TEMPLATE_FORM_FIELDS = ("template_source", "template_upload", + "template_data", "template_url", + "environment_source", "environment_upload", + "environment_data") + LAUNCH_STACK_FORM_FIELDS = ("stack_name", "timeout_mins", + "enable_rollback", "password") + + @tables.bind_table_action('launch') + def select_template(self, launch_button): + launch_button.click() + return forms.FormRegion( + self.driver, self.conf, + field_mappings=self.SELECT_TEMPLATE_FORM_FIELDS) + + def launch_stack(self): + return forms.FormRegion(self.driver, self.conf, + field_mappings=self.LAUNCH_STACK_FORM_FIELDS) + + @tables.bind_table_action('delete') + def delete_stack(self, delete_button): + delete_button.click() + return forms.BaseFormRegion(self.driver, self.conf) + + +class StacksPage(basepage.BaseNavigationPage): + DEFAULT_TEMPLATE_SOURCE = 'raw' + + CONFIG = config.get_config() + DEFAULT_PASSWORD = CONFIG.identity.admin_password + STACKS_TABLE_NAME_COLUMN = 'name' + STACKS_TABLE_STATUS_COLUMN = 'stack_status' + + def __init__(self, driver, conf): + super(StacksPage, self).__init__(driver, conf) + self._page_title = "Stacks" + + @property + def stacks_table(self): + return StacksTable(self.driver, self.conf) + + def _get_row_with_stack_name(self, name): + return self.stacks_table.get_row(self.STACKS_TABLE_NAME_COLUMN, name) + + def create_stack(self, stack_name, template_data, + template_source=DEFAULT_TEMPLATE_SOURCE, + environment_source=None, + environment_upload=None, + timeout_mins=None, + enable_rollback=None, + password=DEFAULT_PASSWORD): + select_template_form = self.stacks_table.select_template() + select_template_form.template_source.value = template_source + select_template_form.template_data.text = template_data + select_template_form.submit() + launch_stack_form = self.stacks_table.launch_stack() + launch_stack_form.stack_name.text = stack_name + launch_stack_form.password.text = password + launch_stack_form.submit() + + def delete_stack(self, name): + row = self._get_row_with_stack_name(name) + row.mark() + confirm_delete_stacks_form = self.stacks_table.delete_stack() + confirm_delete_stacks_form.submit() + + def is_stack_present(self, name): + return bool(self._get_row_with_stack_name(name)) + + def is_stack_create_complete(self, name): + row = self._get_row_with_stack_name(name) + return self.stacks_table.wait_cell_status( + lambda: row and row.cells[self.STACKS_TABLE_STATUS_COLUMN], + 'Create Complete') + + def is_stack_deleted(self, name): + return self.stacks_table.is_row_deleted( + lambda: self._get_row_with_stack_name(name)) diff --git a/openstack_dashboard/test/integration_tests/tests/test-data/stack_template b/openstack_dashboard/test/integration_tests/tests/test-data/stack_template new file mode 100644 index 0000000000..a86e585286 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test-data/stack_template @@ -0,0 +1,11 @@ +heat_template_version: 2013-05-23 +description: Simple template to deploy a single compute instance +resources: + my_instance: + type: OS::Nova::Server + properties: + key_name: {0} + image: {1} + flavor: m1.tiny + networks: + - network: {2} \ No newline at end of file diff --git a/openstack_dashboard/test/integration_tests/tests/test_stacks.py b/openstack_dashboard/test/integration_tests/tests/test_stacks.py new file mode 100644 index 0000000000..b60476b77c --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test_stacks.py @@ -0,0 +1,70 @@ +# 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 os + +from openstack_dashboard.test.integration_tests import decorators +from openstack_dashboard.test.integration_tests import helpers +from openstack_dashboard.test.integration_tests.regions import messages + + +class TestStacks(helpers.AdminTestCase): + KEYPAIR_NAME = 'keypair_for_stack' + STACKS_NAME = helpers.gen_random_resource_name('stack', timestamp=False) + STACK_TEMPLATE_PATH = os.path.join( + os.path.dirname(__file__), 'test-data/stack_template') + + def setUp(self): + super(TestStacks, self).setUp() + keypair_page = self.home_pg.\ + go_to_compute_accessandsecurity_keypairspage() + keypair_page.create_keypair(self.KEYPAIR_NAME) + keypair_page = self.home_pg.\ + go_to_compute_accessandsecurity_keypairspage() + self.assertTrue(keypair_page.is_keypair_present(self.KEYPAIR_NAME)) + + @decorators.services_required("heat") + def test_create_delete_stack(self): + """tests the stack creation and deletion functionality + * creates a new stack + * verifies the stack appears in the stacks table in Create Complete + state + * deletes the newly created stack + * verifies the stack does not appear in the table after deletion + """ + with open(self.STACK_TEMPLATE_PATH, 'r') as f: + template = f.read() + input_template = template.format(self.KEYPAIR_NAME, + self.CONFIG.image.images_list[0], + "public") + stacks_page = self.home_pg.go_to_orchestration_stackspage() + + stacks_page.create_stack(self.STACKS_NAME, input_template) + self.assertTrue( + stacks_page.find_message_and_dismiss(messages.INFO)) + self.assertFalse( + stacks_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(stacks_page.is_stack_present(self.STACKS_NAME)) + self.assertTrue(stacks_page.is_stack_create_complete(self.STACKS_NAME)) + + stacks_page.delete_stack(self.STACKS_NAME) + self.assertTrue( + stacks_page.find_message_and_dismiss(messages.SUCCESS)) + self.assertFalse( + stacks_page.find_message_and_dismiss(messages.ERROR)) + self.assertTrue(stacks_page.is_stack_deleted(self.STACKS_NAME)) + + def tearDown(self): + keypair_page = self.home_pg.\ + go_to_compute_accessandsecurity_keypairspage() + keypair_page.delete_keypairs(self.KEYPAIR_NAME) + keypair_page.find_message_and_dismiss(messages.SUCCESS) + super(TestStacks, self).tearDown() diff --git a/tools/gate/integration/devstack_exports.sh b/tools/gate/integration/devstack_exports.sh deleted file mode 100644 index ce5593e0c9..0000000000 --- a/tools/gate/integration/devstack_exports.sh +++ /dev/null @@ -1,6 +0,0 @@ -export PYTHONUNBUFFERED=true -export DEVSTACK_GATE_TIMEOUT=90 -export DEVSTACK_GATE_TEMPEST=0 -export DEVSTACK_GATE_EXERCISES=0 -export DEVSTACK_GATE_INSTALL_TESTONLY=1 -export DEVSTACK_GATE_NEUTRON=1 diff --git a/tools/gate/integration/devstack_gate_rc b/tools/gate/integration/devstack_gate_rc new file mode 100644 index 0000000000..1579b49f69 --- /dev/null +++ b/tools/gate/integration/devstack_gate_rc @@ -0,0 +1,4 @@ +# This file contains various customized Devstack settings that Horizon uses at +# gate for integration tests job + +export ENABLED_SERVICES=heat,h-eng,h-api,h-api-cfn,h-api-cw