from navmazing import NavigateToSibling, NavigateToAttribute
from widgetastic_manageiq import UpDownSelect, PaginationPane, SummaryFormItem, Table
from widgetastic_patternfly import (
BootstrapSelect, Button, Input, Tab, CheckableBootstrapTreeview,
BootstrapSwitch, CandidateNotFound, Dropdown)
from widgetastic.utils import VersionPick, Version
from widgetastic.widget import Checkbox, View, Text
from cfme.base.credential import Credential
from cfme.base.ui import ConfigurationView
from cfme.exceptions import OptionNotAvailable, RBACOperationBlocked
from cfme.utils.appliance import Navigatable
from cfme.utils.appliance.implementations.ui import navigator, CFMENavigateStep, navigate_to
from cfme.utils.log import logger
from cfme.utils.pretty import Pretty
from cfme.utils.update import Updateable
from cfme.utils.wait import wait_for
[docs]def simple_user(userid, password):
creds = Credential(principal=userid, secret=password)
return User(name=userid, credential=creds)
####################################################################################################
# RBAC USER METHODS
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[docs]class UsersEntities(View):
table = Table('//div[@id=\'records_div\']//table')
[docs]class AllUserView(ConfigurationView):
""" All Users View."""
toolbar = View.nested(AccessControlToolbar)
entities = View.nested(UsersEntities)
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Access Control EVM Users'
)
[docs]class AddUserView(UserForm):
""" Add User View."""
add_button = Button('Add')
@property
def is_displayed(self):
return self.accordions.accesscontrol.is_opened and self.title.text == "Adding a new User"
[docs]class DetailsUserView(ConfigurationView):
""" User Details view."""
toolbar = View.nested(AccessControlToolbar)
@property
def is_displayed(self):
return (
self.title.text == 'EVM User "{}"'.format(self.context['object'].name) and
self.accordions.accesscontrol.is_opened
)
[docs]class EditUserView(UserForm):
""" User Edit View."""
save_button = Button('Save')
reset_button = Button('Reset')
change_stored_password = Text('#change_stored_password')
cancel_password_change = Text('#cancel_password_change')
@property
def is_displayed(self):
return (
self.title.text == 'Editing User "{}"'.format(self.context['object'].name) and
self.accordions.accesscontrol.is_opened
)
[docs]class User(Updateable, Pretty, Navigatable):
""" Class represents an user in CFME UI
Args:
name: Name of the user
credential: User's credentials
email: User's email
group: User's group for assigment
cost_center: User's cost center
value_assign: user's value to assign
appliance: appliance under test
"""
pretty_attrs = ['name', 'group']
def __init__(self, name=None, credential=None, email=None, group=None, cost_center=None,
value_assign=None, appliance=None):
Navigatable.__init__(self, appliance=appliance)
self.name = name
self.credential = credential
self.email = email
self.group = group
self.cost_center = cost_center
self.value_assign = value_assign
self._restore_user = None
def __enter__(self):
if self._restore_user != self.appliance.user:
logger.info('Switching to new user: %s', self.credential.principal)
self._restore_user = self.appliance.user
self.appliance.server.logout()
self.appliance.user = self
def __exit__(self, *args, **kwargs):
if self._restore_user != self.appliance.user:
logger.info('Restoring to old user: %s', self._restore_user.credential.principal)
self.appliance.server.logout()
self.appliance.user = self._restore_user
self._restore_user = None
[docs] def create(self, cancel=False):
""" User creation method
Args:
cancel: True - if you want to cancel user creation,
by defaul user will be created
Throws:
RBACOperationBlocked: If operation is blocked due to current user
not having appropriate permissions OR update is not allowed
for currently selected role
"""
if self.appliance.version < "5.8":
user_blocked_msg = ("Userid has already been taken")
else:
user_blocked_msg = ("Userid is not unique within region {}".format(
self.appliance.server_region()))
view = navigate_to(self, 'Add')
view.fill({
'name_txt': self.name,
'userid_txt': self.credential.principal,
'password_txt': self.credential.secret,
'password_verify_txt': self.credential.verify_secret,
'email_txt': self.email,
'user_group_select': getattr(self.group, 'description', None)
})
if cancel:
view.cancel_button.click()
flash_message = 'Add of new User was cancelled by the user'
else:
view.add_button.click()
flash_message = 'User "{}" was saved'.format(self.name)
try:
view.flash.assert_message(user_blocked_msg)
raise RBACOperationBlocked(user_blocked_msg)
except AssertionError:
pass
view = self.create_view(AllUserView)
view.flash.assert_success_message(flash_message)
assert view.is_displayed
[docs] def update(self, updates):
""" Update user method
Args:
updates: user data that should be changed
Note: In case updates is the same as original user data, update will be canceled,
as 'Save' button will not be active
"""
view = navigate_to(self, 'Edit')
self.change_stored_password()
new_updates = {}
if 'credential' in updates:
new_updates.update({
'userid_txt': updates.get('credential').principal,
'password_txt': updates.get('credential').secret,
'password_verify_txt': updates.get('credential').verify_secret
})
new_updates.update({
'name_txt': updates.get('name'),
'email_txt': updates.get('email'),
'user_group_select': getattr(
updates.get('group'),
'description', None)
})
changed = view.fill({
'name_txt': new_updates.get('name_txt'),
'userid_txt': new_updates.get('userid_txt'),
'password_txt': new_updates.get('password_txt'),
'password_verify_txt': new_updates.get('password_verify_txt'),
'email_txt': new_updates.get('email_txt'),
'user_group_select': new_updates.get('user_group_select')
})
if changed:
view.save_button.click()
flash_message = 'User "{}" was saved'.format(updates.get('name', self.name))
else:
view.cancel_button.click()
flash_message = 'Edit of User was cancelled by the user'
view = self.create_view(DetailsUserView, override=updates)
view.flash.assert_message(flash_message)
assert view.is_displayed
[docs] def copy(self):
""" Creates copy of existing user
return: User object of copied user
"""
view = navigate_to(self, 'Details')
view.toolbar.configuration.item_select('Copy this User to a new User')
view = self.create_view(AddUserView)
new_user = User(name="{}copy".format(self.name),
credential=Credential(principal='redhat', secret='redhat'))
view.fill({
'name_txt': new_user.name,
'userid_txt': new_user.credential.principal,
'password_txt': new_user.credential.secret,
'password_verify_txt': new_user.credential.verify_secret
})
view.add_button.click()
view = self.create_view(AllUserView)
view.flash.assert_success_message('User "{}" was saved'.format(new_user.name))
assert view.is_displayed
return new_user
[docs] def delete(self, cancel=True):
"""Delete existing user
Args:
cancel: Default value 'True', user will be deleted
'False' - deletion of user will be canceled
Throws:
RBACOperationBlocked: If operation is blocked due to current user
not having appropriate permissions OR delete is not allowed
for currently selected user
"""
flash_success_msg = 'EVM User "{}": Delete successful'.format(self.name)
flash_blocked_msg = "Default EVM User \"{}\" cannot be deleted".format(self.name)
delete_user_txt = 'Delete this User'
view = navigate_to(self, 'Details')
if not view.toolbar.configuration.item_enabled(delete_user_txt):
raise RBACOperationBlocked("Configuration action '{}' is not enabled".format(
delete_user_txt))
view.toolbar.configuration.item_select(delete_user_txt, handle_alert=cancel)
try:
view.flash.assert_message(flash_blocked_msg)
raise RBACOperationBlocked(flash_blocked_msg)
except AssertionError:
pass
view.flash.assert_message(flash_success_msg)
if cancel:
view = self.create_view(AllUserView)
view.flash.assert_success_message(flash_success_msg)
else:
view = self.create_view(DetailsUserView)
assert view.is_displayed
[docs] def remove_tag(self, tag, value):
""" Remove tag from existing user
Args:
tag: Tag category
value: Tag name
"""
view = navigate_to(self, 'EditTags')
row = view.tag_table.row(category=tag, assigned_value=value)
row[0].click()
view.save_button.click()
view = self.create_view(DetailsUserView)
view.flash.assert_success_message('Tag edits were successfully saved')
assert view.is_displayed
# TODO update elements, after 1469035 fix
[docs] def change_stored_password(self, changes=None, cancel=False):
""" Changes user password
Args:
changes: dict with fields to be changes,
if None, passwords fields only be anabled
cancel: True, if you want to disable password change
"""
view = navigate_to(self, 'Edit')
self.browser.execute_script(
self.browser.get_attribute(
'onClick', self.browser.element(view.change_stored_password)))
if changes:
view.fill(changes)
if cancel:
self.browser.execute_script(
self.browser.get_attribute(
'onClick', self.browser.element(view.cancel_password_change)))
@property
def exists(self):
try:
navigate_to(self, 'Details')
return True
except CandidateNotFound:
return False
@property
def description(self):
return self.credential.principal
@navigator.register(User, 'All')
[docs]class UserAll(CFMENavigateStep):
VIEW = AllUserView
prerequisite = NavigateToAttribute('appliance.server', 'Configuration')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Users')
@navigator.register(User, 'Add')
[docs]class UserAdd(CFMENavigateStep):
VIEW = AddUserView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select("Add a new User")
@navigator.register(User, 'Details')
[docs]class UserDetails(CFMENavigateStep):
VIEW = DetailsUserView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Users', self.obj.name)
@navigator.register(User, 'Edit')
[docs]class UserEdit(CFMENavigateStep):
VIEW = EditUserView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select('Edit this User')
@navigator.register(User, 'EditTags')
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# RBAC USER METHODS
####################################################################################################
####################################################################################################
# RBAC GROUP METHODS
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[docs]class AddGroupView(GroupForm):
""" Add Group View in CFME UI """
add_button = Button("Add")
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == "Adding a new Group"
)
[docs]class DetailsGroupView(ConfigurationView):
""" Details Group View in CFME UI """
toolbar = View.nested(AccessControlToolbar)
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'EVM Group "{}"'.format(self.context['object'].description)
)
[docs]class EditGroupView(GroupForm):
""" Edit Group View in CFME UI """
save_button = Button("Save")
reset_button = Button('Reset')
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Editing Group "{}"'.format(self.context['object'].description)
)
[docs]class AllGroupView(ConfigurationView):
""" All Groups View in CFME UI """
toolbar = View.nested(AccessControlToolbar)
table = Table("//div[@id='main_div']//table")
paginator = PaginationPane()
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Access Control EVM Groups'
)
[docs]class EditGroupSequenceView(ConfigurationView):
""" Edit Groups Sequence View in CFME UI """
group_order_selector = UpDownSelect(
'#seq_fields',
'//button[@title="Move selected fields up"]/i',
'//button[@title="Move selected fields down"]/i')
save_button = Button('Save')
reset_button = Button('Reset')
cancel_button = Button('Cancel')
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == "Editing Sequence of User Groups"
)
[docs]class Group(Updateable, Pretty, Navigatable):
"""Represents a group in CFME UI
Args:
description: group description
role: group role
tenant: group tenant
user_to_lookup: ldap user to lookup
ldap_credentials: ldap user credentials
tag: tag for group restriction
host_cluster: host/cluster for group restriction
vm_template: vm/template for group restriction
appliance: appliance under test
"""
pretty_attrs = ['description', 'role']
def __init__(self, description=None, role=None, tenant="My Company", user_to_lookup=None,
ldap_credentials=None, tag=None, host_cluster=None, vm_template=None,
appliance=None):
Navigatable.__init__(self, appliance=appliance)
self.description = description
self.role = role
self.tenant = tenant
self.ldap_credentials = ldap_credentials
self.user_to_lookup = user_to_lookup
self.tag = tag
self.host_cluster = host_cluster
self.vm_template = vm_template
[docs] def create(self, cancel=False):
""" Create group method
Args:
cancel: True - if you want to cancel group creation,
by default group will be created
Throws:
RBACOperationBlocked: If operation is blocked due to current user
not having appropriate permissions OR delete is not allowed
for currently selected user
"""
if self.appliance.version < "5.8":
flash_blocked_msg = ("Description has already been taken")
else:
flash_blocked_msg = "Description is not unique within region {}".format(
self.appliance.server_region())
view = navigate_to(self, 'Add')
view.fill({
'description_txt': self.description,
'role_select': self.role,
'group_tenant': self.tenant
})
self._set_group_restriction(view.my_company_tags, self.tag)
self._set_group_restriction(view.hosts_and_clusters, self.host_cluster)
self._set_group_restriction(view.vms_and_templates, self.vm_template)
if cancel:
view.cancel_button.click()
flash_message = 'Add of new Group was cancelled by the user'
else:
view.add_button.click()
flash_message = 'Group "{}" was saved'.format(self.description)
view = self.create_view(AllGroupView)
try:
view.flash.assert_message(flash_blocked_msg)
raise RBACOperationBlocked(flash_blocked_msg)
except AssertionError:
pass
view.flash.assert_success_message(flash_message)
assert view.is_displayed
def _retrieve_ldap_user_groups(self):
""" Retrive ldap user groups
return: AddGroupView
"""
view = navigate_to(self, 'Add')
view.fill({'lookup_ldap_groups_chk': True,
'user_to_look_up': self.user_to_lookup,
'username': self.ldap_credentials.principal,
'password': self.ldap_credentials.secret})
view.retrieve_button.click()
return view
def _retrieve_ext_auth_user_groups(self):
""" Retrive external authorization user groups
return: AddGroupView
"""
view = navigate_to(self, 'Add')
view.fill({'lookup_ldap_groups_chk': True,
'user_to_look_up': self.user_to_lookup})
view.retrieve_button.click()
return view
def _fill_ldap_group_lookup(self, view):
""" Fills ldap info for group lookup
Args: view: view for group creation(AddGroupView)
"""
view.fill({'ldap_groups_for_user': self.description,
'description_txt': self.description,
'role_select': self.role,
'group_tenant': self.tenant})
view.add_button.click()
view = self.create_view(AllGroupView)
view.flash.assert_success_message('Group "{}" was saved'.format(self.description))
assert view.is_displayed
[docs] def add_group_from_ldap_lookup(self):
"""Adds a group from ldap lookup"""
view = self._retrieve_ldap_user_groups()
self._fill_ldap_group_lookup(view)
[docs] def add_group_from_ext_auth_lookup(self):
"""Adds a group from external authorization lookup"""
view = self._retrieve_ext_auth_user_groups()
self._fill_ldap_group_lookup(view)
[docs] def update(self, updates):
""" Update group method
Args:
updates: group data that should be changed
Note: In case updates is the same as original group data, update will be canceled,
as 'Save' button will not be active
"""
edit_group_txt = 'Edit this Group'
view = navigate_to(self, 'Details')
if not view.toolbar.configuration.item_enabled(edit_group_txt):
raise RBACOperationBlocked("Configuration action '{}' is not enabled".format(
edit_group_txt))
view = navigate_to(self, 'Edit')
changed = view.fill({
'description_txt': updates.get('description'),
'role_select': updates.get('role'),
'group_tenant': updates.get('tenant')
})
changed_tag = self._set_group_restriction(view.my_company_tags, updates.get('tag'), True)
changed_host_cluster = self._set_group_restriction(
view.hosts_and_clusters, updates.get('host_cluster'), True)
changed_vm_template = self._set_group_restriction(
view.vms_and_templates, updates.get('vm_template'), True)
if changed or changed_tag or changed_host_cluster or changed_vm_template:
view.save_button.click()
flash_message = 'Group "{}" was saved'.format(
updates.get('description', self.description))
else:
view.cancel_button.click()
flash_message = 'Edit of Group was cancelled by the user'
view = self.create_view(DetailsGroupView, override=updates)
view.flash.assert_message(flash_message)
assert view.is_displayed
[docs] def delete(self, cancel=True):
"""
Delete existing group
Args:
cancel: Default value 'True', group will be deleted
'False' - deletion of group will be canceled
Throws:
RBACOperationBlocked: If operation is blocked due to current user
not having appropriate permissions OR delete is not allowed
for currently selected group
"""
flash_success_msg = 'EVM Group "{}": Delete successful'.format(self.description)
flash_blocked_msg_list = [
('EVM Group "{}": '
'Error during delete: A read only group cannot be deleted.'.format(
self.description)),
('EVM Group "{}": Error during delete: '
'The group has users assigned that do not '
'belong to any other group'.format(self.description))]
delete_group_txt = 'Delete this Group'
view = navigate_to(self, 'Details')
if not view.toolbar.configuration.item_enabled(delete_group_txt):
raise RBACOperationBlocked("Configuration action '{}' is not enabled".format(
delete_group_txt))
view.toolbar.configuration.item_select(delete_group_txt, handle_alert=cancel)
for flash_blocked_msg in flash_blocked_msg_list:
try:
view.flash.assert_message(flash_blocked_msg)
raise RBACOperationBlocked(flash_blocked_msg)
except AssertionError:
pass
view.flash.assert_no_error()
view.flash.assert_message(flash_success_msg)
if cancel:
view = self.create_view(AllGroupView)
view.flash.assert_success_message(flash_success_msg)
else:
view = self.create_view(DetailsGroupView)
assert view.is_displayed, (
"Access Control Group {} Detail View is not displayed".format(self.description))
[docs] def remove_tag(self, tag, value):
""" Delete tag for existing group
Args:
tag: Tag category
value: Tag name
"""
view = navigate_to(self, 'EditTags')
row = view.tag_table.row(category=tag, assigned_value=value)
row[0].click()
view.save_button.click()
view = self.create_view(DetailsGroupView)
view.flash.assert_success_message('Tag edits were successfully saved')
assert view.is_displayed
[docs] def set_group_order(self, updated_order):
""" Sets group order for group lookup
Args:
updated_order: group order list
"""
name_column = "Name"
find_row_kwargs = {name_column: self.description}
view = navigate_to(self, 'All')
row = view.paginator.find_row_on_pages(view.table, **find_row_kwargs)
original_sequence = row.sequence.text
original_order = self.group_order[:len(updated_order)]
view = self.create_view(EditGroupSequenceView)
assert view.is_displayed
# We pick only the same amount of items for comparing
if updated_order == original_order:
return # Ignore that, would cause error on Save click
view.group_order_selector.fill(updated_order)
view.save_button.click()
view = self.create_view(AllGroupView)
assert view.is_displayed
row = view.paginator.find_row_on_pages(view.table, **find_row_kwargs)
changed_sequence = row.sequence.text
assert original_sequence != changed_sequence, "{} Group Edit Sequence Failed".format(
self.description)
def _set_group_restriction(self, tab_view, item, update=False):
""" Sets tag/host/template restriction for the group
Args:
tab_view: tab view
item: path to check box that should be selected/deselected
update: If True - checkbox state will be updated
Returns: True - if update is successful
"""
updated_result = False
if item is not None:
if update:
if tab_view.tree.node_checked(*item):
tab_view.tree.uncheck_node(*item)
else:
tab_view.tree.check_node(*item)
updated_result = True
else:
tab_view.tree.fill(item)
return updated_result
@property
def group_order(self):
view = navigate_to(Group, 'EditGroupSequence')
return view.group_order_selector.items
@property
def exists(self):
try:
navigate_to(self, 'Details')
return True
except CandidateNotFound:
return False
@navigator.register(Group, 'All')
[docs]class GroupAll(CFMENavigateStep):
VIEW = AllGroupView
prerequisite = NavigateToAttribute('appliance.server', 'Configuration')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Groups')
@navigator.register(Group, 'Add')
[docs]class GroupAdd(CFMENavigateStep):
VIEW = AddGroupView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select("Add a new Group")
@navigator.register(Group, 'EditGroupSequence')
[docs]class EditGroupSequence(CFMENavigateStep):
VIEW = EditGroupSequenceView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select(
'Edit Sequence of User Groups for LDAP Look Up')
@navigator.register(Group, 'Details')
[docs]class GroupDetails(CFMENavigateStep):
VIEW = DetailsGroupView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Groups', self.obj.description)
@navigator.register(Group, 'Edit')
[docs]class GroupEdit(CFMENavigateStep):
VIEW = EditGroupView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select('Edit this Group')
@navigator.register(Group, 'EditTags')
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# END RBAC GROUP METHODS
####################################################################################################
####################################################################################################
# RBAC ROLE METHODS
####################################################################################################
[docs]class AddRoleView(RoleForm):
""" Add Role View """
add_button = Button('Add')
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Adding a new Role'
)
[docs]class EditRoleView(RoleForm):
""" Edit Role View """
save_button = Button('Save')
reset_button = Button('Reset')
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Editing Role "{}"'.format(self.context['object'].name)
)
[docs]class DetailsRoleView(RoleForm):
""" Details Role View """
toolbar = View.nested(AccessControlToolbar)
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Role "{}"'.format(self.context['object'].name)
)
[docs]class AllRolesView(ConfigurationView):
""" All Roles View """
toolbar = View.nested(AccessControlToolbar)
table = Table("//div[@id='main_div']//table")
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Access Control Roles'
)
[docs]class Role(Updateable, Pretty, Navigatable):
""" Represents a role in CFME UI
Args:
name: role name
vm_restriction: restriction used for role
product_features: product feature to select
appliance: appliance unter test
"""
pretty_attrs = ['name', 'product_features']
def __init__(self, name=None, vm_restriction=None, product_features=None, appliance=None):
Navigatable.__init__(self, appliance=appliance)
self.name = name
self.vm_restriction = vm_restriction
self.product_features = product_features or []
[docs] def create(self, cancel=False):
""" Create role method
Args:
cancel: True - if you want to cancel role creation,
by default, role will be created
Throws:
RBACOperationBlocked: If operation is blocked due to current user
not having appropriate permissions OR update is not allowed
for currently selected role
"""
flash_blocked_msg = "Name has already been taken"
view = navigate_to(self, 'Add')
view.fill({'name_txt': self.name,
'vm_restriction_select': self.vm_restriction})
self.set_role_product_features(view, self.product_features)
if cancel:
view.cancel_button.click()
flash_message = 'Add of new Role was cancelled by the user'
else:
view.add_button.click()
flash_message = 'Role "{}" was saved'.format(self.name)
view = self.create_view(AllRolesView)
try:
view.flash.assert_message(flash_blocked_msg)
raise RBACOperationBlocked(flash_blocked_msg)
except AssertionError:
pass
view.flash.assert_success_message(flash_message)
assert view.is_displayed
[docs] def update(self, updates):
""" Update role method
Args:
updates: role data that should be changed
Note: In case updates is the same as original role data, update will be canceled,
as 'Save' button will not be active
"""
flash_blocked_msg = "Read Only Role \"{}\" can not be edited".format(self.name)
edit_role_txt = 'Edit this Role'
view = navigate_to(self, 'Details')
if not view.toolbar.configuration.item_enabled(edit_role_txt):
raise RBACOperationBlocked("Configuration action '{}' is not enabled".format(
edit_role_txt))
view = navigate_to(self, 'Edit')
try:
view.flash.assert_message(flash_blocked_msg)
raise RBACOperationBlocked(flash_blocked_msg)
except AssertionError:
pass
changed = view.fill({
'name_txt': updates.get('name'),
'vm_restriction_select': updates.get('vm_restriction')
})
feature_changed = self.set_role_product_features(view, updates.get('product_features'))
if changed or feature_changed:
view.save_button.click()
flash_message = 'Role "{}" was saved'.format(updates.get('name', self.name))
else:
view.cancel_button.click()
flash_message = 'Edit of Role was cancelled by the user'
view = self.create_view(DetailsRoleView, override=updates)
view.flash.assert_message(flash_message)
assert view.is_displayed
[docs] def delete(self, cancel=True):
""" Delete existing role
Args:
cancel: Default value 'True', role will be deleted
'False' - deletion of role will be canceled
Throws:
RBACOperationBlocked: If operation is blocked due to current user
not having appropriate permissions OR delete is not allowed
for currently selected role
"""
flash_blocked_msg = ("Role \"{}\": Error during delete: Cannot delete record "
"because of dependent entitlements".format(self.name))
flash_success_msg = 'Role "{}": Delete successful'.format(self.name)
delete_role_txt = 'Delete this Role'
view = navigate_to(self, 'Details')
if not view.toolbar.configuration.item_enabled(delete_role_txt):
raise RBACOperationBlocked("Configuration action '{}' is not enabled".format(
delete_role_txt))
view.toolbar.configuration.item_select(delete_role_txt, handle_alert=cancel)
try:
view.flash.assert_message(flash_blocked_msg)
raise RBACOperationBlocked(flash_blocked_msg)
except AssertionError:
pass
view.flash.assert_message(flash_success_msg)
if cancel:
view = self.create_view(AllRolesView)
view.flash.assert_success_message(flash_success_msg)
else:
view = self.create_view(DetailsRoleView)
assert view.is_displayed
[docs] def copy(self, name=None):
""" Creates copy of existing role
Returns: Role object of copied role
"""
if name is None:
name = "{}_copy".format(self.name)
view = navigate_to(self, 'Details')
view.toolbar.configuration.item_select('Copy this Role to a new Role')
view = self.create_view(AddRoleView)
new_role = Role(name=name)
view.fill({'name_txt': new_role.name})
view.add_button.click()
view = self.create_view(AllRolesView)
view.flash.assert_success_message('Role "{}" was saved'.format(new_role.name))
assert view.is_displayed
return new_role
[docs] def set_role_product_features(self, view, product_features):
""" Sets product features for role restriction
Args:
view: AddRoleView or EditRoleView
product_features: list of product features with options to select
"""
feature_update = False
if product_features is not None and isinstance(product_features, (list, tuple, set)):
for path, option in product_features:
if option:
view.product_features_tree.check_node(*path)
else:
view.product_features_tree.uncheck_node(*path)
feature_update = True
return feature_update
@navigator.register(Role, 'All')
[docs]class RoleAll(CFMENavigateStep):
VIEW = AllRolesView
prerequisite = NavigateToAttribute('appliance.server', 'Configuration')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Roles')
@navigator.register(Role, 'Add')
[docs]class RoleAdd(CFMENavigateStep):
VIEW = AddRoleView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select("Add a new Role")
@navigator.register(Role, 'Details')
[docs]class RoleDetails(CFMENavigateStep):
VIEW = DetailsRoleView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Roles', self.obj.name)
@navigator.register(Role, 'Edit')
[docs]class RoleEdit(CFMENavigateStep):
VIEW = EditRoleView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select('Edit this Role')
####################################################################################################
# RBAC TENANT METHODS
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[docs]class TenantQuotaView(ConfigurationView):
""" Tenant Quota View """
form = View.nested(TenantQuotaForm)
save_button = Button('Save')
reset_button = Button('Reset')
cancel_button = Button('Cancel')
@property
def is_displayed(self):
return (
self.form.template_cb.is_displayed and
self.title.text == 'Manage quotas for Tenant "{}"'.format(self.context['object'].name)
)
[docs]class AllTenantView(ConfigurationView):
""" All Tenants View """
toolbar = View.nested(AccessControlToolbar)
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Access Control Tenants'
)
[docs]class AddTenantView(TenantForm):
""" Add Tenant View """
add_button = Button('Add')
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Adding a new Tenant'
)
[docs]class DetailsTenantView(ConfigurationView):
""" Details Tenant View """
toolbar = View.nested(AccessControlToolbar)
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Tenant "{}"'.format(self.context['object'].name)
)
[docs]class ParentDetailsTenantView(DetailsTenantView):
""" Parent Tenant Details View """
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Tenant "{}"'.format(self.context['object'].parent_tenant.name)
)
[docs]class EditTenantView(TenantForm):
""" Edit Tenant View """
save_button = Button('Save')
reset_button = Button('Reset')
@property
def is_displayed(self):
return (
self.accordions.accesscontrol.is_opened and
self.title.text == 'Editing Tenant "{}"'.format(self.context['object'].name)
)
[docs]class Tenant(Updateable, Pretty, Navigatable):
""" Class representing CFME tenants in the UI.
* Kudos to mfalesni *
The behaviour is shared with Project, which is the same except it cannot create more nested
tenants/projects.
Args:
name: Name of the tenant
description: Description of the tenant
parent_tenant: Parent tenant, can be None, can be passed as string or object
"""
pretty_attrs = ["name", "description"]
@classmethod
[docs] def get_root_tenant(cls):
return cls(name="My Company", _default=True)
def __init__(self, name=None, description=None, parent_tenant=None, _default=False,
appliance=None):
Navigatable.__init__(self, appliance=appliance)
self.name = name
self.description = description
self.parent_tenant = parent_tenant
self._default = _default
@property
def parent_tenant(self):
if self._default:
return None
if self._parent_tenant:
return self._parent_tenant
return self.get_root_tenant()
@parent_tenant.setter
def parent_tenant(self, tenant):
if tenant is not None and isinstance(tenant, Project):
# If we try to
raise ValueError("Project cannot be a parent object.")
if isinstance(tenant, basestring):
# If parent tenant is passed as string,
# we assume that tenant name was passed instead of object
tenant = Tenant(tenant)
self._parent_tenant = tenant
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
else:
return self.name == other.name
@property
def exists(self):
try:
navigate_to(self, 'Details')
return True
except CandidateNotFound:
return False
@property
def tree_path(self):
if self._default:
return [self.name]
else:
return self.parent_tenant.tree_path + [self.name]
@property
def parent_path(self):
return self.tree_path[:-1]
[docs] def create(self, cancel=False):
""" Create role method
Args:
cancel: True - if you want to cancel role creation,
by defaul(False), role will be created
"""
if self._default:
raise ValueError("Cannot create the root tenant {}".format(self.name))
view = navigate_to(self, 'Add')
view.fill({'name': self.name,
'description': self.description})
if cancel:
view.cancel_button.click()
tenant_flash_message = 'Add of new Tenant was cancelled by the user'
project_flash_message = 'Add of new Project was cancelled by the user'
else:
view.add_button.click()
tenant_flash_message = 'Tenant "{}" was saved'.format(self.name)
project_flash_message = 'Project "{}" was saved'.format(self.name)
view = self.create_view(ParentDetailsTenantView)
if isinstance(self, Tenant):
view.flash.assert_success_message(tenant_flash_message)
elif isinstance(self, Project):
view.flash.assert_success_message(project_flash_message)
else:
raise TypeError(
'No Tenant or Project class passed to create method{}'.format(
type(self).__name__))
assert view.is_displayed
[docs] def update(self, updates):
""" Update tenant/project method
Args:
updates: tenant/project data that should be changed
Note: In case updates is the same as original tenant/project data, update will be canceled,
as 'Save' button will not be active
"""
view = navigate_to(self, 'Edit')
changed = view.fill(updates)
if changed:
view.save_button.click()
flash_message = 'Project "{}" was saved'.format(updates.get('name', self.name))
else:
view.cancel_button.click()
flash_message = 'Edit of Project "{}" was cancelled by the user'.format(
updates.get('name', self.name))
view = self.create_view(DetailsTenantView, override=updates)
view.flash.assert_message(flash_message)
assert view.is_displayed
[docs] def delete(self, cancel=True):
""" Delete existing role
Args:
cancel: Default value 'True', role will be deleted
'False' - deletion of role will be canceled
"""
view = navigate_to(self, 'Details')
view.toolbar.configuration.item_select(
'Delete this item', handle_alert=cancel)
if cancel:
view = self.create_view(ParentDetailsTenantView)
view.flash.assert_success_message(
'Tenant "{}": Delete successful'.format(self.description))
else:
view = self.create_view(DetailsRoleView)
assert view.is_displayed
[docs] def set_quota(self, **kwargs):
""" Sets tenant quotas """
view = navigate_to(self, 'ManageQuotas')
wait_for(lambda: view.is_displayed, fail_condition=False, num_sec=5, delay=0.5)
view.form.fill({'cpu_cb': kwargs.get('cpu_cb'),
'cpu_txt': kwargs.get('cpu'),
'memory_cb': kwargs.get('memory_cb'),
'memory_txt': kwargs.get('memory'),
'storage_cb': kwargs.get('storage_cb'),
'storage_txt': kwargs.get('storage'),
'vm_cb': kwargs.get('vm_cb'),
'vm_txt': kwargs.get('vm'),
'template_cb': kwargs.get('template_cb'),
'template_txt': kwargs.get('template')})
view.save_button.click()
view = self.create_view(DetailsTenantView)
view.flash.assert_success_message('Quotas for Tenant "{}" were saved'.format(self.name))
assert view.is_displayed
@navigator.register(Tenant, 'All')
[docs]class TenantAll(CFMENavigateStep):
VIEW = AllTenantView
prerequisite = NavigateToAttribute('appliance.server', 'Configuration')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Tenants')
@navigator.register(Tenant, 'Details')
[docs]class TenantDetails(CFMENavigateStep):
VIEW = DetailsTenantView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Tenants', *self.obj.tree_path)
@navigator.register(Tenant, 'Add')
[docs]class TenantAdd(CFMENavigateStep):
VIEW = AddTenantView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.accordions.accesscontrol.tree.click_path(
self.obj.appliance.server_region_string(), 'Tenants', *self.obj.parent_path)
if isinstance(self.obj, Tenant):
add_selector = 'Add child Tenant to this Tenant'
elif isinstance(self.obj, Project):
add_selector = 'Add Project to this Tenant'
else:
raise OptionNotAvailable('Object type unsupported for Tenant Add: {}'
.format(type(self.obj).__name__))
self.prerequisite_view.toolbar.configuration.item_select(add_selector)
@navigator.register(Tenant, 'Edit')
[docs]class TenantEdit(CFMENavigateStep):
VIEW = EditTenantView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select('Edit this item')
@navigator.register(Tenant, 'ManageQuotas')
[docs]class TenantManageQuotas(CFMENavigateStep):
VIEW = TenantQuotaView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.toolbar.configuration.item_select('Manage Quotas')
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# END TENANT METHODS
####################################################################################################
####################################################################################################
# RBAC PROJECT METHODS
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[docs]class Project(Tenant):
""" Class representing CFME projects in the UI.
Project cannot create more child tenants/projects.
Args:
name: Name of the project
description: Description of the project
parent_tenant: Parent project, can be None, can be passed as string or object
"""
pass
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# END PROJECT METHODS
####################################################################################################