diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py index 7fde9ce969..a8dad67991 100644 --- a/openstack_dashboard/test/integration_tests/config.py +++ b/openstack_dashboard/test/integration_tests/config.py @@ -79,6 +79,15 @@ ScenarioGroup = [ help='ssh username for image file'), ] +InstancesGroup = [ + cfg.StrOpt('available_zone', + default='nova', + help="Zone to be selected for launch Instances"), + cfg.StrOpt('image_name', + default='cirros-0.3.4-x86_64-uec (24.0 MB)', + help="Boot Source to be selected for launch Instances"), +] + def _get_config_files(): conf_dir = os.path.join( @@ -98,5 +107,6 @@ def get_config(): cfg.CONF.register_opts(SeleniumGroup, group="selenium") cfg.CONF.register_opts(ImageGroup, group="image") cfg.CONF.register_opts(ScenarioGroup, group="scenario") + cfg.CONF.register_opts(InstancesGroup, group="launch_instances") return cfg.CONF diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf index 6bf51c28fe..a7d4e1bbec 100644 --- a/openstack_dashboard/test/integration_tests/horizon.conf +++ b/openstack_dashboard/test/integration_tests/horizon.conf @@ -59,3 +59,9 @@ sahara=False [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) diff --git a/openstack_dashboard/test/integration_tests/pages/project/compute/instancespage.py b/openstack_dashboard/test/integration_tests/pages/project/compute/instancespage.py new file mode 100644 index 0000000000..bb202d95dc --- /dev/null +++ b/openstack_dashboard/test/integration_tests/pages/project/compute/instancespage.py @@ -0,0 +1,156 @@ +# 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 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 InstancesPage(basepage.BaseNavigationPage): + + DEFAULT_FLAVOR = 'm1.tiny' + DEFAULT_COUNT = 1 + DEFAULT_BOOT_SOURCE = 'Boot from image' + DEFAULT_VOLUME_NAME = None + DEFAULT_SNAPSHOT_NAME = None + DEFAULT_VOLUME_SNAPSHOT_NAME = None + DEFAULT_DELETE_ON_TERMINATE = False + DEFAULT_SECURITY_GROUP = True + + _instances_table_locator = (by.By.CSS_SELECTOR, 'table#instances') + + INSTANCES_TABLE_NAME = "instances" + INSTANCES_TABLE_ACTIONS = ("launch_ng", "launch", "terminate", + ('start', 'stop', "reboot")) + INSTANCES_TABLE_NAME_COLUMN_INDEX = 0 + INSTANCES_TABLE_STATUS_COLUMN_INDEX = 5 + INSTANCES_TABLE_ROW_ACTIONS = { + tables.ComplexActionRowRegion.PRIMARY_ACTION: "create_snapshot", + tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ( + "associate_floating_ip", "disassociate_floating_ip", + "edit_instance", "edit_security_groups", "console", + "view_log", "pause", "suspend", "resize", "lock", "unlock", + "soft_reboot", "hard_reboot", "shutoff", "rebuild", "terminate") + } + + CREATE_INSTANCE_FORM_FIELDS = (( + "availability_zone", "name", "flavor", + "count", "source_type", "instance_snapshot_id", + "volume_id", "volume_snapshot_id", "image_id", "volume_size", + "delete_on_terminate"), + ("keypair", "groups"), + ("script_source", "script_upload", "script_data"), + ("disk_config", "config_drive") + ) + + def __init__(self, driver, conf): + super(InstancesPage, self).__init__(driver, conf) + self._page_title = "Instances" + + def _get_row_with_instance_name(self, name): + return self.instances_table.get_row( + self.INSTANCES_TABLE_NAME_COLUMN_INDEX, name) + + @property + def instances_table(self): + src_elem = self._get_element(*self._instances_table_locator) + return tables.ComplexActionTableRegion(self.driver, + self.conf, src_elem, + self.INSTANCES_TABLE_NAME, + self.INSTANCES_TABLE_ACTIONS, + self.INSTANCES_TABLE_ROW_ACTIONS + ) + + @property + def confirm_delete_instances_form(self): + return forms.BaseFormRegion(self.driver, self.conf, None) + + @property + def create_instance_form(self): + return forms.TabbedFormRegion(self.driver, self.conf, None, + self.CREATE_INSTANCE_FORM_FIELDS) + + @property + def delete_instance_form(self): + return forms.BaseFormRegion(self.driver, self.conf, None) + + def is_instance_present(self, name): + return bool(self._get_row_with_instance_name(name)) + + def create_instance(self, instance_name, + available_zone=None, + instance_count=DEFAULT_COUNT, + flavor=DEFAULT_FLAVOR, + boot_source=DEFAULT_BOOT_SOURCE, + source_name=None, + device_size=None, + delete_on_terminate=DEFAULT_DELETE_ON_TERMINATE): + if not available_zone: + available_zone = self.conf.launch_instances.available_zone + self.instances_table.launch.click() + instance = self.create_instance_form + instance.availability_zone.value = available_zone + instance.name.text = instance_name + instance.flavor.text = flavor + instance.count.value = instance_count + instance.source_type.text = boot_source + boot_source = self._get_source_name(instance, boot_source, + self.conf.launch_instances) + if not source_name: + source_name = boot_source[1] + boot_source[0].text = source_name + if device_size: + instance.volume_size.value = device_size + if delete_on_terminate: + instance.delete_on_terminate.mark() + instance.submit.click() + self._wait_till_spinner_disappears() + + def terminate_instance(self, name): + row = self._get_row_with_instance_name(name) + row.mark() + self.instances_table.terminate.click() + self.confirm_delete_instances_form.submit.click() + self._wait_till_spinner_disappears() + + def is_instance_terminated(self, name): + try: + row = self._get_row_with_instance_name(name) + self._wait_till_element_disappears(row) + except exceptions.TimeoutException: + return False + return True + + def is_instance_active(self, name): + row = self._get_row_with_instance_name(name) + + def cell_getter(): + return row.cells[self.INSTANCES_TABLE_STATUS_COLUMN_INDEX] + try: + self._wait_till_text_present_in_element(cell_getter, 'Active') + except exceptions.TimeoutException: + return False + return True + + def _get_source_name(self, instance, boot_source, + conf): + if 'image' in boot_source: + return instance.image_id, conf.image_name + elif boot_source == 'Boot from volume': + return instance.volume_id, self.DEFAULT_VOLUME_NAME + elif boot_source == 'Boot from snapshot': + return instance.instance_snapshot_id, self.DEFAULT_SNAPSHOT_NAME + elif 'volume snapshot (creates a new volume)' in boot_source: + return (instance.volume_snapshot_id, + self.DEFAULT_VOLUME_SNAPSHOT_NAME) diff --git a/openstack_dashboard/test/integration_tests/regions/forms.py b/openstack_dashboard/test/integration_tests/regions/forms.py index c004f6ef22..6dd71194fc 100644 --- a/openstack_dashboard/test/integration_tests/regions/forms.py +++ b/openstack_dashboard/test/integration_tests/regions/forms.py @@ -93,7 +93,7 @@ class CheckBoxFormFieldRegion(BaseFormFieldRegion, CheckBoxMixin): """Checkbox field.""" _element_locator = (by.By.CSS_SELECTOR, - 'div > label > input[type=checkbox]') + 'label > input[type=checkbox]') class ProjectPageCheckBoxFormFieldRegion(BaseFormFieldRegion, CheckBoxMixin): diff --git a/openstack_dashboard/test/integration_tests/tests/test_instances.py b/openstack_dashboard/test/integration_tests/tests/test_instances.py new file mode 100644 index 0000000000..a7398f7170 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test_instances.py @@ -0,0 +1,28 @@ +# 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 + +INSTANCES_NAME = helpers.gen_random_resource_name('instance', + timestamp=False) + + +class TestInstances(helpers.AdminTestCase): + """This is a basic scenario to test: + * Create Instance and Terminate Instance + """ + + def test_create_terminate_instance(self): + instances_page = self.home_pg.go_to_compute_instancespage() + instances_page.create_instance(INSTANCES_NAME) + self.assertTrue(instances_page.is_instance_active(INSTANCES_NAME)) + instances_page.terminate_instance(INSTANCES_NAME) + self.assertTrue(instances_page.is_instance_terminated(INSTANCES_NAME))