547 lines
22 KiB
Python
547 lines
22 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
|
|
|
|
import pytest
|
|
|
|
from oslo_utils import uuidutils
|
|
|
|
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', 'demo')
|
|
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', 'demo')
|
|
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', 'demo')
|
|
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', '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', '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', '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', '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] == 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
|
|
current_page_definition = widgets.get_image_table_definition(driver)
|
|
assert current_page_definition is None
|
|
|
|
|
|
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', '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)
|
|
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', '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', '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)
|
|
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', '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', '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)
|
|
driver.find_element_by_xpath(
|
|
"//button[@class='btn btn-primary finish']").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
|