
Until now we have used implicit timeout for message checking. It is enough for the majority of the cases. But in cases of overloaded deployment and more consuming actions (like manage volume attachments) this timeout is enough for all the actions except for messages so individual message timeout (adjustable through horizon.conf) seems to be the best solution. Change-Id: I6e43cdfa9fc02c1346b5a8228caacaf154e20ba3 Signed-off-by: Jan Jasek <jjasek@redhat.com>
556 lines
23 KiB
Python
556 lines
23 KiB
Python
# 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
|
|
import tempfile
|
|
import time
|
|
|
|
from oslo_utils import uuidutils
|
|
import pytest
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from selenium.webdriver.support.wait import WebDriverWait
|
|
|
|
from openstack_dashboard.test.selenium.integration import test_instances
|
|
from openstack_dashboard.test.selenium import widgets
|
|
|
|
# Imported fixtures
|
|
clear_instance_admin = test_instances.clear_instance_admin
|
|
|
|
|
|
@pytest.fixture(params=[1])
|
|
def image_names(request):
|
|
count = request.param
|
|
img_name_list = ['horizon_img_%s' % uuidutils.generate_uuid(dashed=False)]
|
|
if count > 1:
|
|
img_name_list = [f"{img_name_list[0]}-{item}"
|
|
for item in range(1, count + 1)]
|
|
return img_name_list
|
|
|
|
|
|
@pytest.fixture
|
|
def new_image_demo(image_names, temporary_file, openstack_demo):
|
|
for img in image_names:
|
|
image = openstack_demo.create_image(
|
|
img,
|
|
disk_format="raw",
|
|
filename=temporary_file,
|
|
wait=True,
|
|
)
|
|
yield image
|
|
for img in image_names:
|
|
openstack_demo.delete_image(img)
|
|
|
|
|
|
@pytest.fixture
|
|
def new_image_admin(image_names, temporary_file, openstack_admin):
|
|
for img in image_names:
|
|
image = openstack_admin.create_image(
|
|
img,
|
|
disk_format="raw",
|
|
filename=temporary_file,
|
|
wait=True,
|
|
)
|
|
yield image
|
|
for img in image_names:
|
|
openstack_admin.delete_image(img)
|
|
|
|
|
|
@pytest.fixture
|
|
def new_protected_image_admin(image_names, temporary_file, openstack_admin):
|
|
for img in image_names:
|
|
image = openstack_admin.create_image(
|
|
img,
|
|
disk_format="raw",
|
|
filename=temporary_file,
|
|
is_protected=True,
|
|
wait=True,
|
|
)
|
|
yield image
|
|
for img in image_names:
|
|
openstack_admin.delete_image(img)
|
|
|
|
|
|
@pytest.fixture
|
|
def clear_image_demo(image_names, openstack_demo):
|
|
yield None
|
|
for img in image_names:
|
|
openstack_demo.delete_image(
|
|
img,
|
|
wait=True,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def clear_image_admin(image_names, openstack_admin):
|
|
yield None
|
|
for img in image_names:
|
|
openstack_admin.delete_image(
|
|
img,
|
|
wait=True,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def temporary_file(tmp_path):
|
|
"""Generate temporary file.
|
|
|
|
:return: path to the generated file
|
|
"""
|
|
with tempfile.NamedTemporaryFile(suffix='.qcow2',
|
|
dir=tmp_path) as tmp_file:
|
|
tmp_file.write(os.urandom(5000))
|
|
yield tmp_file.name
|
|
|
|
|
|
def wait_for_steady_state_of_unprotected_image(openstack, image_name):
|
|
for attempt in range(3):
|
|
image_attributes_details = openstack.image.find_image(image_name)
|
|
if image_attributes_details["status"] == "active" \
|
|
and image_attributes_details["protected"] is False:
|
|
break
|
|
else:
|
|
time.sleep(2)
|
|
|
|
|
|
def wait_for_angular_readiness(driver, config):
|
|
driver.set_script_timeout(config.selenium.page_timeout)
|
|
driver.execute_async_script("""
|
|
var callback = arguments[arguments.length - 1];
|
|
var element = document.querySelector('div.btn-group[name="protected"]');
|
|
if (!window.angular) {
|
|
callback(false)
|
|
}
|
|
if (angular.getTestability) {
|
|
angular.getTestability(element).whenStable(function(){callback(true)});
|
|
} else {
|
|
if (!angular.element(element).injector()) {
|
|
callback(false)
|
|
}
|
|
var browser = angular.element(element).injector().get('$browser');
|
|
browser.notifyWhenNoOutstandingRequests(function(){callback(true)});
|
|
};""")
|
|
|
|
|
|
def test_image_create_from_local_file_demo(login, driver, image_names,
|
|
temporary_file, clear_image_demo,
|
|
config, openstack_demo):
|
|
image_name = image_names[0]
|
|
login('user')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
driver.find_element_by_xpath(
|
|
"//button[normalize-space()='Create Image']").click()
|
|
wizard = driver.find_element_by_css_selector("wizard")
|
|
wizard.find_element_by_id("imageForm-name").send_keys(image_name)
|
|
select_element = wizard.find_element_by_css_selector(
|
|
"input[name='image_file']")
|
|
select_element.send_keys(temporary_file)
|
|
wizard.find_element_by_id("imageForm-format").click()
|
|
wizard.find_element_by_css_selector(
|
|
"[label='QCOW2 - QEMU Emulator']").click()
|
|
wizard.find_element_by_css_selector("button.btn-primary.finish").click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert (f"Success: Image {image_name} was successfully"
|
|
f" created." in messages)
|
|
assert openstack_demo.compute.find_image(image_name) is not None
|
|
|
|
|
|
def test_image_delete_demo(login, driver, image_names, openstack_demo,
|
|
new_image_demo, config):
|
|
image_name = image_names[0]
|
|
login('user')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
widgets.select_from_dropdown(actions_column, "Delete Image")
|
|
widgets.confirm_modal(driver)
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert f"Success: Deleted Image: {image_name}." in messages
|
|
assert openstack_demo.compute.find_image(image_name) is None
|
|
|
|
|
|
@pytest.mark.parametrize('image_names', [2], indirect=True)
|
|
def test_image_pagination_demo(login, driver, image_names, openstack_demo,
|
|
change_page_size_demo, new_image_demo, config):
|
|
items_per_page = 1
|
|
img_list = sorted([item["name"]
|
|
for item in openstack_demo.compute.images()])
|
|
first_page_definition = widgets.TableDefinition(next=True, prev=False,
|
|
count=items_per_page,
|
|
names=[img_list[0]])
|
|
second_page_definition = widgets.TableDefinition(next=True, prev=True,
|
|
count=items_per_page,
|
|
names=[img_list[1]])
|
|
third_page_definition = widgets.TableDefinition(next=False, prev=True,
|
|
count=items_per_page,
|
|
names=[img_list[2]])
|
|
login('user')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
actual_page1_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert first_page_definition == actual_page1_definition
|
|
# Turning to next page(page2)
|
|
driver.find_element_by_link_text("Next »").click()
|
|
actual_page2_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert second_page_definition == actual_page2_definition
|
|
# Turning to next page(page3)
|
|
driver.find_element_by_link_text("Next »").click()
|
|
actual_page3_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert third_page_definition == actual_page3_definition
|
|
# Turning back to previous page(page2)
|
|
driver.find_element_by_link_text("« Prev").click()
|
|
actual_page2_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert second_page_definition == actual_page2_definition
|
|
# Turning back to previous page(page1)
|
|
driver.find_element_by_link_text("« Prev").click()
|
|
actual_page1_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert first_page_definition == actual_page1_definition
|
|
|
|
|
|
# Admin tests
|
|
|
|
|
|
def test_image_create_from_local_file_admin(login, driver, image_names,
|
|
temporary_file, clear_image_admin,
|
|
config, openstack_admin):
|
|
image_name = image_names[0]
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
driver.find_element_by_xpath(
|
|
"//button[normalize-space()='Create Image']").click()
|
|
wizard = driver.find_element_by_css_selector("wizard")
|
|
wizard.find_element_by_id("imageForm-name").send_keys(image_name)
|
|
select_element = wizard.find_element_by_css_selector(
|
|
"input[name='image_file']")
|
|
select_element.send_keys(temporary_file)
|
|
wizard.find_element_by_id("imageForm-format").click()
|
|
wizard.find_element_by_css_selector(
|
|
"[label='QCOW2 - QEMU Emulator']").click()
|
|
wizard.find_element_by_css_selector("button.btn-primary.finish").click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert (f"Success: Image {image_name} was successfully"
|
|
f" created." in messages)
|
|
assert openstack_admin.compute.find_image(image_name) is not None
|
|
|
|
|
|
def test_image_delete_admin(login, driver, image_names, openstack_admin,
|
|
new_image_admin, config):
|
|
image_name = image_names[0]
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
widgets.select_from_dropdown(actions_column, "Delete Image")
|
|
widgets.confirm_modal(driver)
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert f"Success: Deleted Image: {image_name}." in messages
|
|
assert openstack_admin.compute.find_image(image_name) is None
|
|
|
|
|
|
@pytest.mark.parametrize('image_names', [2], indirect=True)
|
|
def test_image_pagination_admin(login, driver, image_names, openstack_admin,
|
|
change_page_size_admin, new_image_admin,
|
|
config):
|
|
items_per_page = 1
|
|
img_list = sorted([item["name"]
|
|
for item in openstack_admin.compute.images()])
|
|
first_page_definition = widgets.TableDefinition(next=True, prev=False,
|
|
count=items_per_page,
|
|
names=[img_list[0]])
|
|
second_page_definition = widgets.TableDefinition(next=True, prev=True,
|
|
count=items_per_page,
|
|
names=[img_list[1]])
|
|
third_page_definition = widgets.TableDefinition(next=False, prev=True,
|
|
count=items_per_page,
|
|
names=[img_list[2]])
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
actual_page1_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert first_page_definition == actual_page1_definition
|
|
# Turning to next page(page2)
|
|
driver.find_element_by_link_text("Next »").click()
|
|
actual_page2_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert second_page_definition == actual_page2_definition
|
|
# Turning to next page(page3)
|
|
driver.find_element_by_link_text("Next »").click()
|
|
actual_page3_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert third_page_definition == actual_page3_definition
|
|
# Turning back to previous page(page2)
|
|
driver.find_element_by_link_text("« Prev").click()
|
|
actual_page2_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert second_page_definition == actual_page2_definition
|
|
# Turning back to previous page(page1)
|
|
driver.find_element_by_link_text("« Prev").click()
|
|
actual_page1_definition = widgets.get_image_table_definition(driver,
|
|
sorting=True)
|
|
assert first_page_definition == actual_page1_definition
|
|
|
|
|
|
def test_image_filtration_admin(login, driver, new_image_admin, config):
|
|
image_name = new_image_admin.name
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
filter_input_field = driver.find_element_by_css_selector(".search-input")
|
|
filter_input_field.send_keys(image_name)
|
|
# Fetch page definition after filtration
|
|
current_page_definition = widgets.get_image_table_definition(driver)
|
|
assert vars(current_page_definition)['names'][0].text == image_name
|
|
assert vars(current_page_definition)['count'] == 1
|
|
filter_input_field.clear()
|
|
# Generate random non existent image name
|
|
random_img_name = 'horizon_img_%s' % uuidutils.generate_uuid(dashed=False)
|
|
filter_input_field.send_keys(random_img_name)
|
|
# Fetch page definition after filtration
|
|
no_items_present = driver.find_element_by_xpath(
|
|
"//*[normalize-space()='No items to display.']")
|
|
assert no_items_present
|
|
|
|
|
|
def test_remove_protected_image_admin(login, driver, image_names,
|
|
new_protected_image_admin, config,
|
|
openstack_admin):
|
|
image_name = new_protected_image_admin.name
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
menu_button = actions_column.find_element_by_css_selector(
|
|
".dropdown-toggle"
|
|
)
|
|
menu_button.click()
|
|
options = actions_column.find_elements_by_css_selector(
|
|
"ul.dropdown-menu li")
|
|
for option in options:
|
|
if option.text == "Delete Image":
|
|
pytest.fail("Delete option should not exist")
|
|
actions_column.find_element_by_xpath(
|
|
".//*[normalize-space()='Edit Image']").click()
|
|
wait_for_angular_readiness(driver, config)
|
|
image_form = driver.find_element_by_css_selector(".ng-wizard")
|
|
image_form.find_element_by_xpath(".//label[text()='No']").click()
|
|
image_form.find_element_by_xpath(
|
|
".//button[@class='btn btn-primary finish']").click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert f"Success: Image {image_name} was successfully updated." in messages
|
|
wait_for_steady_state_of_unprotected_image(openstack_admin, image_name)
|
|
WebDriverWait(driver, config.selenium.page_timeout).until(
|
|
EC.invisibility_of_element(
|
|
(By.CLASS_NAME, "modal modal-dialog-wizard fade ng-scope "
|
|
"ng-isolate-scope ng-animate ng-leave ng-leave-active")))
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
widgets.select_from_dropdown(actions_column, "Delete Image")
|
|
widgets.confirm_modal(driver)
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert f"Success: Deleted Image: {image_name}." in messages
|
|
assert openstack_admin.compute.find_image(image_name) is None
|
|
|
|
|
|
def test_edit_image_description_admin(login, driver, image_names,
|
|
new_image_admin, config,
|
|
openstack_admin):
|
|
image_name = new_image_admin.name
|
|
new_description = "new_description_text"
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
widgets.select_from_dropdown(actions_column, "Edit Image")
|
|
wait_for_angular_readiness(driver, config)
|
|
image_form = driver.find_element_by_css_selector(".ng-wizard")
|
|
desc_field = image_form.find_element_by_css_selector(
|
|
"#imageForm-description")
|
|
|
|
desc_field.clear()
|
|
desc_field.send_keys(new_description)
|
|
image_form.find_element_by_xpath(
|
|
".//button[@class='btn btn-primary finish']").click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert f"Success: Image {image_name} " \
|
|
f"was successfully updated." in messages
|
|
image_id = new_image_admin.id
|
|
assert (openstack_admin.compute.get(f"/images/{image_id}").json(
|
|
)['image']['metadata']['description'] == new_description)
|
|
|
|
|
|
def test_update_image_metadata_admin(login, driver,
|
|
new_image_admin, config,
|
|
openstack_admin):
|
|
new_metadata = {
|
|
'metadata1': 'img_metadata%s' % uuidutils.generate_uuid(dashed=False),
|
|
'metadata2': 'img_metadata%s' % uuidutils.generate_uuid(dashed=False)
|
|
}
|
|
image_name = new_image_admin.name
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
widgets.select_from_dropdown(actions_column, "Update Metadata")
|
|
image_form = driver.find_element_by_css_selector(".modal-content")
|
|
for name, value in new_metadata.items():
|
|
image_form.find_element_by_xpath(
|
|
"//input[@name='customItem']").send_keys(name)
|
|
image_form.find_element_by_css_selector(
|
|
"button.btn span[class='fa fa-plus']").click()
|
|
image_form.find_element_by_xpath(
|
|
f"//span[@title='{name}']/parent::div/input").send_keys(value) # noqa: E231,E501
|
|
image_form.find_element_by_xpath(
|
|
"//button[@ng-click='modal.save()']").click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert "Success: Metadata was successfully updated." in messages
|
|
image_id = new_image_admin.id
|
|
for name, value in new_metadata.items():
|
|
assert (openstack_admin.compute.get(f"/images/{image_id}").json(
|
|
)['image']['metadata'][name] == value)
|
|
|
|
|
|
def test_launch_instance_from_image_admin(complete_default_test_network, login,
|
|
driver, instance_name,
|
|
clear_instance_admin, new_image_admin,
|
|
config, openstack_admin):
|
|
image_name = new_image_admin.name
|
|
network = complete_default_test_network.name
|
|
flavor = config.launch_instances.flavor
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
rows[0].find_element_by_xpath(
|
|
"//ng-transclude[normalize-space()='Launch']").click()
|
|
wizard = driver.find_element_by_css_selector("wizard")
|
|
navigation = wizard.find_element_by_css_selector("div.wizard-nav")
|
|
widgets.find_already_visible_element_by_xpath(
|
|
".//*[@id='name']", wizard).send_keys(instance_name)
|
|
navigation.find_element_by_link_text("Networks").click()
|
|
network_table = wizard.find_element_by_css_selector(
|
|
"ng-include[ng-form=launchInstanceNetworkForm]"
|
|
)
|
|
widgets.select_from_transfer_table(network_table, network)
|
|
navigation.find_element_by_link_text("Flavor").click()
|
|
flavor_table = wizard.find_element_by_css_selector(
|
|
"ng-include[ng-form=launchInstanceFlavorForm]"
|
|
)
|
|
widgets.select_from_transfer_table(flavor_table, flavor)
|
|
navigation.find_element_by_link_text("Source").click()
|
|
source_table = wizard.find_element_by_css_selector(
|
|
"ng-include[ng-form=launchInstanceSourceForm]"
|
|
)
|
|
test_instances.delete_volume_on_instance_delete(source_table, "Yes")
|
|
wizard.find_element_by_css_selector(
|
|
"button.btn-primary.finish").click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert "Info: Scheduled creation of 1 instance." in messages
|
|
assert openstack_admin.compute.find_server(instance_name) is not None
|
|
|
|
|
|
def test_create_volume_from_image_admin(login, driver, volume_name,
|
|
new_image_admin, clear_volume_admin,
|
|
config, openstack_admin):
|
|
volume_name = volume_name[0]
|
|
image_name = new_image_admin.name
|
|
login('admin')
|
|
url = '/'.join((
|
|
config.dashboard.dashboard_url,
|
|
'project',
|
|
'images',
|
|
))
|
|
driver.get(url)
|
|
rows = driver.find_elements_by_xpath(f"//a[text()='{image_name}']")
|
|
assert len(rows) == 1
|
|
actions_column = rows[0].find_element_by_xpath(
|
|
".//ancestor::tr/td[contains(@class,'actions_column')]")
|
|
widgets.select_from_dropdown(actions_column, "Create Volume")
|
|
name_field = driver.find_element_by_xpath("//input[@name='name']")
|
|
name_field.clear()
|
|
name_field.send_keys(volume_name)
|
|
create_vol_btn = WebDriverWait(driver, config.selenium.page_timeout).until(
|
|
EC.element_to_be_clickable(
|
|
(By.XPATH, "//button[@class='btn btn-primary finish']")))
|
|
create_vol_btn.click()
|
|
messages = widgets.get_and_dismiss_messages(driver, config)
|
|
assert f"Info: Creating volume {volume_name}" in messages
|
|
assert openstack_admin.block_storage.find_volume(volume_name) is not None
|