""" Page functions for Tenant pages
:var list_page: A :py:class:`cfme.web_ui.Region` object describing elements on the list page.
:var details_page: A :py:class:`cfme.web_ui.Region` object describing elements on the detail page.
"""
from navmazing import NavigateToSibling, NavigateToAttribute
from widgetastic.exceptions import NoSuchElementException
from widgetastic.utils import VersionPick
from widgetastic.widget import View
from widgetastic_patternfly import BootstrapNav, Button, Dropdown, FlashMessages, Input
from widgetastic_manageiq import (
Accordion, BootstrapSelect, BreadCrumb, ItemsToolBarViewSelector, PaginationPane, Search,
SummaryTable, Table, Text, BaseNonInteractiveEntitiesView)
from cfme.base.ui import BaseLoggedInPage
from cfme.exceptions import TenantNotFound, DestinationNotFound, OptionNotAvailable
from cfme.web_ui import match_location
from utils.appliance import Navigatable
from utils.appliance.implementations.ui import CFMENavigateStep, navigator, navigate_to
from utils.log import logger
from utils.wait import wait_for, TimedOutError
from utils.version import Version
[docs]class TenantDetailsAccordion(View):
"""The accordion on the details page"""
@View.nested
class properties(Accordion): # noqa
nav = BootstrapNav('//div[@id="ems_prop"]//ul')
@View.nested
class relationships(Accordion): # noqa
nav = BootstrapNav('//div[@id="ems_rel"]//ul')
[docs]class TenantEntities(View):
"""The entities on the main list page"""
title = Text('//div[@id="main-content"]//h1')
table = Table('//div[@id="list_grid"]//table')
search = View.nested(Search)
# element attributes changed from id to class in upstream-fine+, capture both with locator
flash = FlashMessages('.//div[@id="flash_msg_div"]'
'/div[@id="flash_text_div" or contains(@class, "flash_text_div")]')
[docs]class TenantDetailsEntities(View):
"""The entities on the details page"""
breadcrumb = BreadCrumb()
title = Text('//div[@id="main-content"]//h1')
relationships = SummaryTable(title='Relationships')
quotas = SummaryTable(title='Quotas')
smart_management = SummaryTable(title='Smart Management')
# element attributes changed from id to class in upstream-fine+, capture both with locator
flash = FlashMessages('.//div[@id="flash_msg_div"]'
'/div[@id="flash_text_div" or contains(@class, "flash_text_div")]')
[docs]class TenantEditEntities(View):
"""The entities on the add/edit page"""
breadcrumb = BreadCrumb()
title = Text('//div[@id="main-content"]//h1')
[docs]class TenantEditTagEntities(View):
"""The entities on the edit tags page"""
breadcrumb = BreadCrumb()
title = Text('#explorer_title_text')
included_widgets = View.include(BaseNonInteractiveEntitiesView, use_parent=True)
[docs]class TenantView(BaseLoggedInPage):
"""A base view for all the Tenant pages"""
@property
def in_tenants(self):
"""Determine if the Tenants page is currently open"""
return (
self.logged_in_as_current_user and
self.navigation.currently_selected == ['Compute', 'Clouds', 'Tenants'] and
# TODO: Needs to be converted once there's a Widgetastic alternative
match_location(controller='cloud_tenant', title='Cloud Tenants'))
[docs]class TenantAllView(TenantView):
"""The all tenants page"""
toolbar = View.nested(TenantToolbar)
entities = View.nested(TenantEntities)
paginator = PaginationPane()
@property
def is_displayed(self):
"""This is page currently being displayed"""
return self.in_tenants and self.entities.title.text == 'Cloud Tenants'
[docs]class TenantDetailsView(TenantView):
"""The details page for a tenant"""
toolbar = View.nested(TenantDetailsToolbar)
sidebar = View.nested(TenantDetailsAccordion)
entities = View.nested(TenantDetailsEntities)
@property
def is_displayed(self):
"""Is this page currently being displayed"""
expected_title = '{} (Summary)'.format(self.context['object'].name)
return (
self.in_tenants and
self.entities.title.text == expected_title and
self.entities.breadcrumb.active_location == expected_title)
[docs]class TenantAddView(TenantView):
"""The add page for tenants"""
entities = View.nested(TenantEditEntities)
form = View.nested(TenantAddForm)
@property
def is_displayed(self):
"""Is this page currently being displayed"""
expected_title = 'Add New Cloud Tenant'
return (
self.in_tenants and
self.entities.title.text == expected_title and
self.entities.breadcrumb.active_location == expected_title)
[docs]class TenantEditView(TenantView):
"""The edit page for tenants"""
entities = View.nested(TenantEditEntities)
form = View.nested(TenantEditForm)
@property
def is_displayed(self):
"""Is this page currently being displayed"""
expected_title = 'Edit Cloud Tenant "{}"'.format(self.context['object'].name)
return (
self.in_tenants and
self.entities.title.text == expected_title and
self.entities.breadcrumb.active_location == expected_title)
[docs]class TenantCollection(Navigatable):
"""Collection object for the :py:class:`cfme.cloud.tenant.Tenant`."""
def __init__(self, appliance=None):
self.appliance = appliance
[docs] def instantiate(self, name, provider):
return Tenant(name, provider, collection=self)
[docs] def create(self, name, provider, appliance=None, wait=True):
"""Add a cloud Tenant from the UI and return the Tenant object"""
page = navigate_to(self, 'Add')
changed = page.form.fill({
'cloud_provider': provider.name,
'name': name
})
if changed:
page.form.save_button.click()
else:
page.form.cancel_button.click()
all_view = self.create_view(TenantAllView)
wait_for(lambda: all_view.is_displayed, num_sec=120, delay=3,
fail_func=all_view.flush_widget_cache, handle_exception=True)
if not changed:
if self.appliance.version >= '5.8':
msg = 'Add of Cloud Tenant was cancelled by the user'
else:
msg = 'Add of new Cloud Tenant was cancelled by the user'
all_view.entities.flash.assert_success_message(msg)
else:
all_view.entities.flash.assert_success_message(
'Cloud Tenant "{}" created'.format(name))
tenant = self.instantiate(name, provider)
if wait:
def refresh():
"""Refresh a few things"""
tenant.provider.refresh_provider_relationships()
all_view.flush_widget_cache()
self.browser.refresh()
wait_for(lambda: tenant.exists, timeout=600,
message='Wait for cloud tenant to appear',
delay=10, fail_func=refresh)
return tenant
[docs] def delete(self, *tenants):
"""Delete one or more Tenants from the list of the Tenants
Args:
list of the `cfme.cloud.tenant.Tenant` objects
"""
tenants = list(tenants)
checked_tenants = []
view = navigate_to(self, 'All')
# double check we're in List View
view.toolbar.view_selector.select('List View')
if not view.entities.table.is_displayed:
raise ValueError('No Tenants found')
for row in view.entities.table:
for tenant in tenants:
if tenant.name == row.name.text:
checked_tenants.append(tenant)
row[0].check()
break
if set(tenants) == set(checked_tenants):
break
if set(tenants) != set(checked_tenants):
raise ValueError('Some tenants were not found in the UI')
view.toolbar.configuration.item_select('Delete Cloud Tenants', handle_alert=True)
for tenant in tenants:
tenant.wait_for_disappear()
view.entities.flash.assert_no_error()
# TODO: Assert deletion flash message for selected tenants
# it is not shown in current UI, so not asserting
[docs]class Tenant(Navigatable):
'''Tenant Class'''
_param_name = 'Tenant'
def __init__(self, name, provider, collection=None):
"""Base class for a Tenant"""
self.name = name
self.provider = provider
self.collection = collection or TenantCollection()
Navigatable.__init__(self, appliance=self.collection.appliance)
[docs] def wait_for_disappear(self, timeout=300):
self.provider.refresh_provider_relationships()
try:
return wait_for(lambda: self.exists,
fail_condition=True,
timeout=timeout,
message='Wait for cloud tenant to disappear',
delay=10,
fail_func=self.browser.refresh)
except TimedOutError:
logger.error('Timed out waiting for tenant to disappear, continuing')
[docs] def wait_for_appear(self, timeout=600):
self.provider.refresh_provider_relationships()
return wait_for(lambda: self.exists, timeout=timeout, delay=10,
message='Wait for cloud tenant to appear', fail_func=self.browser.refresh)
[docs] def update(self, updates):
view = navigate_to(self, 'Edit')
updated_name = updates.get('name', self.name + '_edited')
view.form.fill({'name': updated_name})
view.form.save_button.click()
self.provider.refresh_provider_relationships()
return wait_for(lambda: self.exists, fail_condition=False, timeout=600,
message='Wait for cloud tenant to appear', delay=10,
fail_func=self.browser.refresh)
[docs] def delete(self, wait=True):
"""Delete the tenant"""
try:
view = navigate_to(self, 'Details')
except NoSuchElementException as ex:
# Catch general navigation exceptions and raise
raise TenantNotFound(
'Exception while navigating to Tenant details: {}'.format(ex))
view.toolbar.configuration.item_select('Delete Cloud Tenant')
result = view.entities.flash.assert_success_message(
'Delete initiated for 1 Cloud Tenant.')
if wait:
self.provider.refresh_provider_relationships()
result = self.wait_for_disappear(600)
return result
@property
def exists(self):
try:
navigate_to(self, 'Details')
except NoSuchElementException:
return False
else:
return True
@navigator.register(TenantCollection, 'All')
[docs]class TenantAll(CFMENavigateStep):
VIEW = TenantAllView
prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn')
[docs] def step(self, *args, **kwargs):
"""Go to the All page"""
self.prerequisite_view.navigation.select('Compute', 'Clouds', 'Tenants')
[docs] def resetter(self):
"""Reset the view"""
self.view.toolbar.view_selector.select('List View')
if self.view.entities.table.is_displayed:
self.view.paginator.check_all()
self.view.paginator.uncheck_all()
@navigator.register(Tenant, 'Details')
[docs]class TenantDetails(CFMENavigateStep):
VIEW = TenantDetailsView
prerequisite = NavigateToAttribute('collection', 'All')
[docs] def step(self, *args, **kwargs):
"""Navigate to the details page"""
self.prerequisite_view.toolbar.view_selector.select('List View')
row = self.prerequisite_view.paginator.find_row_on_pages(
self.prerequisite_view.entities.table, name=self.obj.name)
row.click()
[docs] def resetter(self):
"""Reset the view"""
self.view.browser.refresh()
@navigator.register(TenantCollection, 'Add')
[docs]class TenantAdd(CFMENavigateStep):
VIEW = TenantAddView
prerequisite = NavigateToSibling('All')
[docs] def step(self, *args, **kwargs):
"""Navigate to the Add page"""
if self.obj.appliance.version >= '5.7':
self.prerequisite_view.toolbar.configuration.item_select('Create Cloud Tenant')
else:
raise DestinationNotFound('Cannot add Cloud Tenants in CFME < 5.7')
@navigator.register(Tenant, 'Edit')
[docs]class TenantEdit(CFMENavigateStep):
VIEW = TenantEditView
prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs):
"""Navigate to the edit page"""
if self.obj.appliance.version >= '5.7':
self.prerequisite_view.toolbar.configuration.item_select('Edit Cloud Tenant')
else:
raise DestinationNotFound('Cannot edit Cloud Tenants in CFME < 5.7')
@navigator.register(Tenant, 'EditTags')