Files
horizon/openstack_dashboard/test/selenium/integration/test_images.py
Dmitriy Rabotyagov 2ec0177edc Use Python 3.12 for python3-django job
With migration from ubuntu jammy to noble, python3.11 is not available
anymore. This makes the job to fail on pre-install step.

So let's use Python 3.12 which is available out of the box on Noble
after switch.

This also bumps pylint version, as older one does not work
anymore with Python 3.12. New pylint brings quite some new
rules with it. Some were disabled, some were fixed within this
patch.

Change-Id: I4ba288966c582910e8a822d4531e29c9c005e48f
2024-12-01 21:48:52 +01:00

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="qcow2",
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="qcow2",
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="qcow2",
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):
driver.set_script_timeout(10)
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)
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)
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)
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)
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(
"//td[text()='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(
f".//*[normalize-space()='Edit Image']").click()
wait_for_angular_readiness(driver)
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)
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)
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)
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)
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)
assert f"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)
assert f"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, f"//button[@class='btn "
f"btn-primary finish']")))
create_vol_btn.click()
messages = widgets.get_and_dismiss_messages(driver)
assert f"Info: Creating volume {volume_name}" in messages
assert openstack_admin.block_storage.find_volume(volume_name) is not None