def get_form(self, blank=False): """Gets a form for a field that already exists (by its name). Or if blank=True, get the form for a new field. Must be on the correct page before calling this. """ idx = "" if blank: row_id = "" # for new entries, id attribute has no trailing '_x' else: idx = sel.get_attribute( "//input[starts-with(@id, 'fields_name') and @value='%s']" % self.name, 'id').split("_")[-1] row_id = "_" + idx def loc(fmt, underscore=True): if blank: plural = "" else: plural = "s" return fmt % (plural, row_id if underscore else row_id.lstrip("_")) def remove(loc): """Return a callable that clicks but still allows popup dismissal""" return lambda _: sel.click(loc, wait_ajax=False) return Form(fields=[ ('name_text', Input(loc('field%s_name%s'))), ('type_select', { version.LOWEST: DHTMLSelect(loc("//div[@id='field%s_aetype_id%s']")), "5.5": AngularSelect(loc("field%s_aetype%s", underscore=False)) }), ('data_type_select', { version.LOWEST: DHTMLSelect(loc("//div[@id='field%s_datatype_id%s']")), "5.5": AngularSelect(loc("field%s_datatype%s", underscore=False)) }), ('default_value_text', Input(loc('field%s_default_value%s'))), ('display_name_text', Input(loc('field%s_display_name%s')) ), ('description_text', Input(loc('field%s_description%s')) ), ('sub_cb', Input(loc('field%s_substitution%s')) ), ('collect_text', Input(loc('field%s_collect%s'))), ('message_text', Input(loc('field%s_message%s')) ), ('on_entry_text', Input(loc('field%s_on_entry%s')) ), ('on_exit_text', Input(loc('field%s_on_exit%s'))), ('max_retries_text', Input(loc('field%s_max_retries%s')) ), ('max_time_text', Input(loc('field%s_max_time%s')) ), ('add_entry_button', "//img[@alt='Add this entry']"), ('remove_entry_button', remove("//a[(contains(@title, 'delete this') or " "contains(@confirm, 'delete field')) and " "contains(@href, 'arr_id=%s')]/img" % idx)) ])
class Timer(object): form = Form(fields=[ ("run", { version.LOWEST: Select("//select[@id='timer_typ']"), '5.5': AngularSelect('timer_typ')}), ("hours", { version.LOWEST: Select("//select[@id='timer_hours']"), '5.5': AngularSelect('timer_hours')}), ("days", { version.LOWEST: Select("//select[@id='timer_days']"), '5.5': AngularSelect('timer_days')}), ("weeks", { version.LOWEST: Select("//select[@id='timer_weeks']"), '5.5': AngularSelect('timer_weeks')}), ("months", { version.LOWEST: Select("//select[@id='timer_months']"), '5.5': AngularSelect('timer_months')}), ("time_zone", { version.LOWEST: Select("//select[@id='time_zone']"), '5.5': AngularSelect('time_zone')}), ("start_date", Calendar("miq_date_1")), ("start_hour", { version.LOWEST: Select("//select[@id='start_hour']"), '5.5': AngularSelect('start_hour')}), ("start_min", { version.LOWEST: Select("//select[@id='start_min']"), '5.5': AngularSelect('start_min')}), ])
def select(self): return version.pick({ version.LOWEST: Select("select#{}".format(self._select_name)), "5.5": AngularSelect(self._select_name) })
def test_broken_angular_select(appliance, request): """Test that checks the fancy selects do not break. Prerequisities: * A fresh downstream appliance Steps: 1) Create a catalog. 2) Create a catalog item, can be Generic and assign the catalog and OSE Installer dialog for testing purposes 3) Try ordering the service, but instead of confirming the form, try changing some select. """ # OSE Installer dialog, one dropdown from it the_select = AngularSelect("param_operatingSystemType") cat = Catalog("Test_catalog_{}".format(fauxfactory.gen_alpha())) cat.create() request.addfinalizer(cat.delete) item = CatalogItem(item_type="Generic", name="Catitem_{}".format(fauxfactory.gen_alpha()), description=fauxfactory.gen_alpha(), display_in=True, catalog=cat, dialog="azure-single-vm-from-user-image") item.create() request.addfinalizer(item.delete) sc = ServiceCatalogs(appliance, item.catalog, item.name) navigate_to(sc, 'Order') # The check itself fill(the_select, "Linux") assert not the_select.is_broken, "The select displayed itself next ot the angular select"
class NewerDashboardWidgetSelector(DashboardWidgetSelector): """Dashboard widget selector from 5.5 onwards.""" _button_open_close = ".//div[contains(@class, 'dropdown-menu open')]" _combo_list = "//div[contains(@class, 'dropdown-menu open')]/ul" _remove_button = ".//a[../../h3[normalize-space(.)='{}']]" _selected = "./div[@id='modules']/div/div/div/h3" _select = AngularSelect("widget") def select(self, *items): sel.wait_for_ajax() for item in items: self._select.select_by_visible_text(item) # Disable some functions def _open_close_combo(self): pass _is_combo_opened = False def _open_combo(self): pass def _close_combo(self): pass @property @contextmanager def combo(self): yield
def test_broken_angular_select(request): """Test that checks the fancy selects do not break. Prerequisities: * A fresh downstream appliance Steps: 1) Create a catalog. 2) Create a catalog item, can be Generic and assign the catalog and OSE Installer dialog for testing purposes 3) Try ordering the service, but instead of confirming the form, try changing some select. """ # OSE Installer dialog, one dropdown from it the_select = AngularSelect("ose_size") cat = Catalog("Test_catalog_{}".format(fauxfactory.gen_alpha())) cat.create() request.addfinalizer(cat.delete) item = CatalogItem(item_type="Generic", name="Catitem_{}".format(fauxfactory.gen_alpha()), description=fauxfactory.gen_alpha(), display_in=True, catalog=cat.name, dialog="OSE Installer") item.create() request.addfinalizer(item.delete) # The check itself pytest.sel.force_navigate("order_service_catalog", context={ "catalog": cat.name, "catalog_item": item }) fill(the_select, "Medium") assert not the_select.is_broken, "The select displayed itself next ot the angular select"
class Role(Updateable, Pretty, Navigatable): form = Form(fields=[ ('name_txt', Input('name')), ('vm_restriction_select', AngularSelect('vm_restriction')), ('product_features_tree', { version.LOWEST: CheckboxTree("//div[@id='features_treebox']/ul"), '5.7': BootstrapTreeview("features_treebox") }), ]) 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 [] def create(self): navigate_to(self, 'Add') fill(self.form, { 'name_txt': self.name, 'vm_restriction_select': self.vm_restriction, 'product_features_tree': self.product_features }, action=form_buttons.add) flash.assert_success_message('Role "{}" was saved'.format(self.name)) def update(self, updates): navigate_to(self, 'Edit') fill(self.form, { 'name_txt': updates.get('name'), 'vm_restriction_select': updates.get('vm_restriction'), 'product_features_tree': updates.get('product_features') }, action=form_buttons.save) flash.assert_success_message('Role "{}" was saved'.format( updates.get('name', self.name))) def delete(self): navigate_to(self, 'Details') tb_select('Delete this Role', invokes_alert=True) sel.handle_alert() flash.assert_success_message('Role "{}": Delete successful'.format( self.name)) def copy(self, name=None): if not name: name = self.name + "copy" navigate_to(self, 'Details') tb.select('Configuration', 'Copy this Role to a new Role') new_role = Role(name=name) fill(self.form, {'name_txt': new_role.name}, action=form_buttons.add) flash.assert_success_message('Role "{}" was saved'.format( new_role.name)) return new_role
def add_button(self): sel.force_navigate('catalog_item', context={ 'catalog': self.catalog, 'catalog_item': self }) cfg_btn('Add a new Button', invokes_alert=True) sel.wait_for_element(button_form.btn_text) fill(button_form, { 'btn_text': "btn_text", 'btn_hvr_text': "btn_descr" }) if current_version() > "5.5": select = AngularSelect("button_image") select.select_by_visible_text("Button Image 1") else: select = DHTMLSelect("div#button_div") select.select_by_value(2) fill( button_form, { 'select_dialog': self.dialog, 'system_process': "Request", 'request': "InspectMe" }) sel.click(button_form.add_button) flash.assert_success_message('Button "btn_descr" was added')
def _fill_recordgrouper(rg, d): logger.info(" Filling {} with data {}".format(str(rg), str(d))) for row_column_name, content in d.iteritems(): row = rg.table.find_row("column_name", row_column_name) if version.current_version() < "5.7": select = sel.element("./select", root=row.calculations) else: select = sel.element("./div/select", root=row.calculations) select_id = sel.get_attribute(select, "id") fill(AngularSelect(select_id, multi=True), content)
def sort_by(sort): """ Changes the sort by field. Args: sort: Value to sort by (visible text in select box) """ _select = version.pick({ version.LOWEST: Select(_locator() + _sort_by), "5.5": AngularSelect('sort_choice')}) sel.select(_select, sel.ByText(str(sort)))
def results_per_page(num): """ Changes the number of results on a page. Args: num: Number of results per page """ _select = version.pick({ version.LOWEST: Select(_locator() + _num_results), "5.5": AngularSelect('ppsetting')}) sel.select(_select, sel.ByText(str(num)))
def create(self): sel.force_navigate('new_button_group', context={"buttongroup": self}) fill(button_group_form, {'btn_group_text': self.text, 'btn_group_hvr_text': self.hover}) if version.current_version() < "5.5": select = DHTMLSelect("div#button_div") else: select = AngularSelect("button_image") select.select_by_value(1) sel.click(button_group_form.add_button) flash.assert_success_message('Buttons Group "{}" was added'.format(self.hover))
def create(self): sel.force_navigate('new_button', context={'buttongroup': self.group}) fill(button_form, {'btn_text': self.text, 'btn_hvr_text': self.hover}) if version.current_version() < "5.5": select = DHTMLSelect("div#button_div") else: select = AngularSelect("button_image") select.select_by_value(2) fill(button_form, {'select_dialog': self.dialog.label if self.dialog is not None else None, 'system_process': self.system, 'request': self.request}) sel.click(button_form.add_button) flash.assert_success_message('Button "{}" was added'.format(self.hover))
class Role(Updateable, Pretty): form = Form( fields=[ ('name_txt', Input('name')), ('vm_restriction_select', { version.LOWEST: Select("//*[@id='vm_restriction']"), '5.5': AngularSelect('vm_restriction')}), ('product_features_tree', CheckboxTree("//div[@id='features_treebox']/ul")), ]) pretty_attrs = ['name', 'product_features'] def __init__(self, name=None, vm_restriction=None, product_features=None): self.name = name self.vm_restriction = vm_restriction self.product_features = product_features or [] def create(self): sel.force_navigate('cfg_accesscontrol_role_add') fill(self.form, {'name_txt': self.name, 'vm_restriction_select': self.vm_restriction, 'product_features_tree': self.product_features}, action=form_buttons.add) flash.assert_success_message('Role "{}" was saved'.format(self.name)) def update(self, updates): sel.force_navigate("cfg_accesscontrol_role_edit", context={"role": self}) fill(self.form, {'name_txt': updates.get('name'), 'vm_restriction_select': updates.get('vm_restriction'), 'product_features_tree': updates.get('product_features')}, action=form_buttons.save) flash.assert_success_message('Role "{}" was saved'.format(updates.get('name', self.name))) def delete(self): sel.force_navigate("cfg_accesscontrol_role_ed", context={"role": self}) tb_select('Delete this Role', invokes_alert=True) sel.handle_alert() flash.assert_success_message('Role "{}": Delete successful'.format(self.name)) def copy(self, name=None): if not name: name = self.name + "copy" sel.force_navigate("cfg_accesscontrol_role_ed", context={"role": self}) tb.select('Configuration', 'Copy this Role to a new Role') new_role = Role(name=name) fill(self.form, {'name_txt': new_role.name}, action=form_buttons.add) flash.assert_success_message('Role "{}" was saved'.format(new_role.name)) return new_role
def set_random_tag(instance): navigate_to(instance, 'Details') toolbar.select('Policy', 'Edit Tags') # select random tag category cat_selector = AngularSelect("tag_cat") random_cat = random.choice(cat_selector.all_options) cat_selector.select_by_value(random_cat.value) # select random tag tag tag_selector = AngularSelect("tag_add") random_tag = random.choice([op for op in tag_selector.all_options if op.value != "select"]) tag_selector.select_by_value(random_tag.value) # Save tag conig form_buttons.save() return Tag(display_name=random_tag.text, category=random_cat.text)
def add_button_group(self): sel.force_navigate('catalog_item', context={'catalog': self.catalog, 'catalog_item': self}) cfg_btn("Add a new Button Group", invokes_alert=True) sel.wait_for_element(button_group_form.btn_group_text) fill(button_group_form, {'btn_group_text': "group_text", 'btn_group_hvr_text': "descr"}) if current_version() > "5.5": select = AngularSelect("button_image") select.select_by_visible_text("Button Image 1") else: select = DHTMLSelect("div#button_div") select.select_by_value(1) sel.click(button_group_form.add_button) flash.assert_success_message('Buttons Group "descr" was added')
def set_random_tag(instance): logger.debug("Setting random tag") navigate_to(instance, 'Details') toolbar.select('Policy', 'Edit Tags') # select random tag category cat_selector = AngularSelect("tag_cat") random_cat = random.choice(cat_selector.all_options) logger.debug("Selected category {cat}".format(cat=random_cat)) cat_selector.select_by_value(random_cat.value) # select random tag tag tag_selector = AngularSelect("tag_add") random_tag = random.choice([op for op in tag_selector.all_options if op.value != "select"]) logger.debug("Selected value {tag}".format(tag=random_tag)) tag_selector.select_by_value(random_tag.value) # Save tag conig form_buttons.save() logger.debug("Tag configuration was saved") return Tag(display_name=random_tag.text, category=random_cat.text)
def add_button_group(self): navigate_to(self, 'Details') cfg_btn("Add a new Button Group", invokes_alert=True) sel.wait_for_element(button_group_form.btn_group_text) fill(button_group_form, {'btn_group_text': "group_text", 'btn_group_hvr_text': "descr"}) if current_version() > "5.5": select = AngularSelect("button_image") select.select_by_visible_text("Button Image 1") else: select = DHTMLSelect("div#button_div") select.select_by_value(1) sel.click(button_group_form.add_button) flash.assert_success_message('Buttons Group "descr" was added')
class CfmeRelationship(object): relationship_form = Form(fields=[( 'server_select', AngularSelect("server_id")), ( 'save_button', form_buttons.save), ( 'reset_button', form_buttons.reset), ('cancel_button', form_buttons.cancel)]) def __init__(self, o): self.o = o def navigate(self): self.o.load_details() cfg_btn('Edit Management Engine Relationship') def is_relationship_set(self): return "<Not a Server>" not in self.get_relationship() def get_relationship(self): self.navigate() rel = str(self.relationship_form.server_select. all_selected_options[0].text) form_buttons.cancel() return rel def set_relationship(self, server_name, server_id, click_cancel=False): self.navigate() option = "{} ({})".format(server_name, server_id) if click_cancel: fill(self.relationship_form, {'server_select': option}, action=self.relationship_form.cancel_button) else: fill(self.relationship_form, {'server_select': option}) sel.click( form_buttons.FormButton("Save Changes", dimmed_alt="Save", force_click=True)) flash.assert_success_message( "Management Engine Relationship saved")
def add_button(self): navigate_to(self, 'Details') cfg_btn('Add a new Button', invokes_alert=True) sel.wait_for_element(button_form.btn_text) fill(button_form, {'btn_text': "btn_text", 'btn_hvr_text': "btn_descr"}) if current_version() > "5.5": select = AngularSelect("button_image") select.select_by_visible_text("Button Image 1") else: select = DHTMLSelect("div#button_div") select.select_by_value(2) fill(button_form, {'select_dialog': self.dialog, 'system_process': "Request", 'request': "InspectMe"}) sel.click(button_form.add_button) flash.assert_success_message('Button "btn_descr" was added')
import cfme.web_ui.tabstrip as tabs import cfme.web_ui.toolbar as tb from cfme.web_ui import (accordion, Quadicon, Form, Input, fill, form_buttons, mixins, Table, Region, AngularSelect, match_location) from utils import version, conf from utils.appliance.implementations.ui import navigator, CFMENavigateStep, navigate_to from utils.appliance import Navigatable from utils.log import logger from utils.pretty import Pretty from utils.update import Updateable from utils.wait import wait_for properties_form = Form(fields=[( 'name_text', Input('name')), ('type_select', AngularSelect("provider_type")), ( 'url_text', Input('url')), ('ssl_checkbox', Input('verify_ssl'))]) credential_form = Form( fields=[('principal_text', Input('log_userid')), ('secret_pass', Input('log_password')), ('verify_secret_pass', Input('log_verify')), ('validate_btn', form_buttons.validate)]) def cfm_mgr_table(): return Table("//div[@id='main_div']//div[@id='list_grid']/table") page = Region(
], 'middleware_provider_detail': [ lambda ctx: sel.click(Quadicon(ctx['provider'].name)), { 'middleware_provider_edit_detail': lambda _: cfg_btn('Edit this Middleware Provider'), 'middleware_provider_timelines_detail': lambda _: mon_btn('Timelines'), 'middleware_provider_edit_tags_detail': lambda _: pol_btn('Edit Tags'), } ] }) properties_form = Form(fields=[( 'type_select', AngularSelect('server_emstype')), ( 'name_text', Input('name')), ('hostname_text', Input('hostname')), ('port_text', Input('port'))]) class HawkularProvider(MiddlewareBase, BaseProvider): """ HawkularProvider class holds provider data. Used to perform actions on hawkular provider page Args: name: Name of the provider hostname: Hostname/IP of the provider port: http/https port of hawkular provider credentials: see Credential inner class. key: The CFME key of the provider in the yaml.
class BaseVM(Pretty, Updateable, PolicyProfileAssignable, Taggable, SummaryMixin, Navigatable): """Base VM and Template class that holds the largest common functionality between VMs, instances, templates and images. In order to inherit these, you have to implement the ``on_details`` method. """ pretty_attrs = ['name', 'provider', 'template_name'] # Forms edit_form = Form(fields=[ ('custom_ident', Input("custom_1")), ('description_tarea', "//textarea[@id='description']"), ('parent_sel', { version.LOWEST: Select("//select[@name='chosen_parent']"), "5.5": AngularSelect("chosen_parent") }), ('child_sel', Select("//select[@id='kids_chosen']", multi=True)), ('vm_sel', Select("//select[@id='choices_chosen']", multi=True)), ('add_btn', "//img[@alt='Move selected VMs to left']"), ('remove_btn', "//img[@alt='Move selected VMs to right']"), ('remove_all_btn', "//img[@alt='Move all VMs to right']"), ]) ### # Factory class methods # @classmethod def factory(cls, vm_name, provider, template_name=None, template=False): """Factory class method that determines the correct subclass for given provider. For reference how does that work, refer to the entrypoints in the setup.py Args: vm_name: Name of the VM/Instance as it appears in the UI provider: The provider object (not the string!) template_name: Source template name. Useful when the VM/Instance does not exist and you want to create it. template: Whether the generated object class should be VM/Instance or a template class. """ try: return all_types(template)[provider.type](vm_name, provider, template_name) except KeyError: # Matching via provider type failed. Maybe we have some generic classes for infra/cloud? try: return all_types(template)[provider.category](vm_name, provider, template_name) except KeyError: raise UnknownProviderType( 'Unknown type of provider CRUD object: {}'.format( provider.__class__.__name__)) ### # To be set or implemented # ALL_LIST_LOCATION = None TO_OPEN_EDIT = None # Name of the item in Configuration that puts you in the form QUADICON_TYPE = "vm" # Titles of the delete buttons in configuration REMOVE_SELECTED = { '5.6': 'Remove selected items', '5.6.2.2': 'Remove selected items from the VMDB', '5.7': 'Remove selected items' } REMOVE_SINGLE = { '5.6': 'Remove Virtual Machine', '5.6.2.2': 'Remove from the VMDB', '5.7': 'Remove Virtual Machine' } RETIRE_DATE_FMT = { version.LOWEST: parsetime.american_date_only_format, '5.7': parsetime.american_minutes_with_utc } _param_name = ParamClassName('name') ### # Shared behaviour # def __init__(self, name, provider, template_name=None, appliance=None): super(BaseVM, self).__init__() Navigatable.__init__(self, appliance=appliance) if type(self) in {BaseVM, VM, Template}: raise NotImplementedError('This class cannot be instantiated.') self.name = name self.provider = provider self.template_name = template_name ### # Properties # @property def is_vm(self): return not isinstance(self, _TemplateMixin) @property def quadicon_type(self): return self.QUADICON_TYPE @property def paged_table(self): _paged_table_template = '//div[@id="list_grid"]/div[@class="{}"]/table/tbody' return version.pick({ version.LOWEST: SplitPagedTable(header_data=(_paged_table_template.format("xhdr"), 1), body_data=(_paged_table_template.format("objbox"), 0)), "5.5": PagedTable('//table'), }) ### # Methods # def check_compliance(self, timeout=240): """Initiates compliance check and waits for it to finish.""" original_state = self.compliance_status cfg_btn("Refresh Relationships and Power States", invokes_alert=True) sel.handle_alert() flash.assert_no_errors() pol_btn("Check Compliance of Last Known Configuration", invokes_alert=True) sel.handle_alert() flash.assert_no_errors() wait_for(lambda: self.compliance_status != original_state, num_sec=timeout, delay=5, message="compliance of {} checked".format(self.name)) return self.compliant @property def compliance_status(self): """Returns the title of the compliance infoblock. The title contains datetime so it can be compared. Returns: :py:class:`NoneType` if no title is present (no compliance checks before), otherwise str """ self.load_details(refresh=True) return InfoBlock("Compliance", "Status").title @property def compliant(self): """Check if the VM is compliant Returns: :py:class:`NoneType` if the VM was never verified, otherwise :py:class:`bool` """ text = self.get_detail(properties=("Compliance", "Status")).strip().lower() if text == "never verified": return None elif text.startswith("non-compliant"): return False elif text.startswith("compliant"): return True else: raise ValueError( "{} is not a known state for compliance".format(text)) @property def console_handle(self): ''' The basic algorithm for getting the consoles window handle is to get the appliances window handle and then iterate through the window_handles till we find one that is not the appliances window handle. Once we find this check that it has a canvas widget with a specific ID ''' browser = self.appliance.browser.widgetastic appliance_handle = browser.window_handle cur_handles = browser.selenium.window_handles logger.info("Current Window Handles: {}".format(cur_handles)) for handle in cur_handles: if handle != appliance_handle: # FIXME: Add code to verify the tab has the correct widget # for a console tab. return handle def delete(self, cancel=False, from_details=False): """Deletes the VM/Instance from the VMDB. Args: cancel: Whether to cancel the action in the alert. from_details: Whether to use the details view or list view. """ if from_details: self.load_details(refresh=True) cfg_btn(self.REMOVE_SINGLE, invokes_alert=True) else: self.find_quadicon().check() cfg_btn(self.REMOVE_SELECTED, invokes_alert=True) sel.handle_alert(cancel=cancel) @property def exists(self): """Checks presence of the quadicon in the CFME.""" try: self.find_quadicon() return True except VmOrInstanceNotFound: return False @property def ip_address(self): """Fetches IP Address of VM""" return self.provider.mgmt.get_ip_address(self.name) @property def is_retired(self): """"Check retirement status of vm""" self.summary.reload() if self.summary.lifecycle.retirement_date.text_value.lower( ) != 'never': try: return self.summary.lifecycle.retirement_state.text_value.lower( ) == 'retired' except AttributeError: return False else: return False def find_quadicon(self, from_any_provider=False, use_search=True): """Find and return a quadicon belonging to a specific vm Args: from_any_provider: Whether to look for it anywhere (root of the tree). Useful when looking up archived or orphaned VMs Returns: entity of appropriate type Raises: VmOrInstanceNotFound """ # todo :refactor this method replace it with vm methods like get_state if from_any_provider: view = navigate_to(self, 'All') else: view = navigate_to(self, 'AllForProvider', use_resetter=False) view.toolbar.view_selector.select('Grid View') if use_search: search.normal_search(self.name) try: return view.entities.get_entity(by_name=self.name, surf_pages=True) except ItemNotFound: raise VmOrInstanceNotFound("VM '{}' not found in UI!".format( self.name)) def get_detail(self, properties=None, icon_href=False): """Gets details from the details infoblock The function first ensures that we are on the detail page for the specific VM/Instance. Args: properties: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Returns: A string representing the contents of the InfoBlock's value. """ self.load_details(refresh=True) if icon_href: return InfoBlock.icon_href(*properties) else: return InfoBlock.text(*properties) def open_console(self, console='VM Console', invokes_alert=False, cancel=False): """ Initiates the opening of one of the console types supported by the Access button. Presently we only support VM Console, which is the HTML5 Console. In case of VMware provider it could be VMRC, VNC/HTML5, WebMKS, but we only support VNC/HTML5. Possible values for 'console' could be 'VM Console' and 'Web Console', but Web Console is not supported as well. Args: console: one of the supported console types given by the Access button. invokes_alert: If the particular console will invoke a CFME popup/alert setting this to true will handle this. cancel: Allows one to cancel the operation if the popup/alert occurs. """ # TODO: implement vmrc vm console if console not in ['VM Console']: raise NotImplementedError( 'Not supported console type: {}'.format(console)) view = navigate_to(self, 'Details') # Click console button given by type view.toolbar.access.item_select( console, handle_alert=None if invokes_alert is False else True) # Get the consoles window handle, and then create a VMConsole object, and store # the VMConsole object aside. console_handle = self.console_handle if console_handle is None: raise TypeError("Console handle should not be None") appliance_handle = self.appliance.browser.widgetastic.window_handle logger.info("Creating VMConsole:") logger.info(" appliance_handle: {}".format(appliance_handle)) logger.info(" console_handle: {}".format(console_handle)) logger.info(" name: {}".format(self.name)) self.vm_console = VMConsole(appliance_handle=appliance_handle, console_handle=console_handle, vm=self) def open_details(self, properties=None): """Clicks on details infoblock""" self.load_details(refresh=True) sel.click(InfoBlock(*properties)) @classmethod def get_first_vm_title(cls, do_not_navigate=False, provider=None): """Get the title of first VM/Instance.""" if not do_not_navigate: if provider is None: navigate_to(cls, 'All') else: provider.load_all_provider_vms() return Quadicon.get_first_quad_title() @property def last_analysed(self): """Returns the contents of the ``Last Analysed`` field in summary""" return self.get_detail(properties=('Lifecycle', 'Last Analyzed')).strip() def load_details(self, refresh=False): """Navigates to an VM's details page. Args: refresh: Refreshes the VM page if already there Raises: VmOrInstanceNotFound: When unable to find the VM passed """ navigate_to(self, 'Details', use_resetter=False) if refresh: toolbar.refresh() self.browser.plugin.ensure_page_safe() def open_edit(self): """Loads up the edit page of the object.""" self.load_details(refresh=True) cfg_btn(self.TO_OPEN_EDIT) def open_timelines(self): """Navigates to an VM's timeline page. Returns: :py:class:`TimelinesView` object """ return navigate_to(self, 'Timelines') def rediscover(self): """Deletes the VM from the provider and lets it discover again""" self.delete(from_details=True) self.wait_for_delete() self.provider.refresh_provider_relationships() self.wait_to_appear() def rediscover_if_analysis_data_present(self): """Rediscovers the object if it has some analysis data present. Returns: Boolean if the rediscovery happened. """ if self.last_analysed.lower() != 'never': self.rediscover() return True return False def refresh_relationships(self, from_details=False, cancel=False, from_any_provider=False): """Executes a refresh of relationships. Args: from_details: Whether or not to perform action from instance details page cancel: Whether or not to cancel the refresh relationships action """ if from_details: self.load_details() else: self.find_quadicon(from_any_provider=from_any_provider).check() cfg_btn('Refresh Relationships and Power States', invokes_alert=True) sel.handle_alert(cancel=cancel) @property def retirement_date(self): """Returns the retirement date of the selected machine, or 'Never' Returns: :py:class:`str` object """ return self.get_detail(properties=("Lifecycle", "Retirement Date")).strip() def smartstate_scan(self, cancel=False, from_details=False): """Initiates fleecing from the UI. Args: cancel: Whether or not to cancel the refresh relationships action from_details: Whether or not to perform action from instance details page """ if from_details: self.load_details(refresh=True) else: self.find_quadicon().check() cfg_btn('Perform SmartState Analysis', invokes_alert=True) sel.handle_alert(cancel=cancel) def wait_to_disappear(self, timeout=600, load_details=True): """Wait for a VM to disappear within CFME Args: timeout: time (in seconds) to wait for it to appear """ wait_for(lambda: self.exists, num_sec=timeout, delay=30, fail_func=sel.refresh, fail_condition=True, message="wait for vm to not exist") wait_for_delete = wait_to_disappear # An alias for more fitting verbosity def wait_to_appear(self, timeout=600, load_details=True): """Wait for a VM to appear within CFME Args: timeout: time (in seconds) to wait for it to appear load_details: when found, should it load the vm details """ wait_for(lambda: self.exists, num_sec=timeout, delay=30, fail_func=self.provider.refresh_provider_relationships, message="wait for vm to appear") if load_details: self.load_details() def set_ownership(self, user=None, group=None, click_cancel=False, click_reset=False): """Set ownership of the VM/Instance or Template/Image""" self.find_quadicon(use_search=False).click() cfg_btn('Set Ownership') if click_reset: action = form_buttons.reset msg_assert = partial(flash.assert_message_match, 'All changes have been reset') elif click_cancel: action = form_buttons.cancel msg_assert = partial(flash.assert_success_message, 'Set Ownership was cancelled by the user') else: action = form_buttons.save msg_assert = partial( flash.assert_success_message, 'Ownership saved for selected {}'.format(self.VM_TYPE)) fill(set_ownership_form, { 'user_name': user, 'group_name': group }, action=action) msg_assert() def unset_ownership(self): """Unset ownership of the VM/Instance or Template/Image""" # choose the vm code comes here self.find_quadicon(use_search=False).click() cfg_btn('Set Ownership') fill(set_ownership_form, { 'user_name': '<No Owner>', 'group_name': 'EvmGroup-administrator' }, action=form_buttons.save) flash.assert_success_message('Ownership saved for selected {}'.format( self.VM_TYPE))
class VM(BaseVM): TO_RETIRE = None retire_form = Form(fields=[( 'date_retire', AngularCalendarInput( "retirement_date", "//label[contains(normalize-space(.), 'Retirement Date')]") ), ('warn', AngularSelect('retirementWarning'))]) def retire(self): self.load_details(refresh=True) lcl_btn(self.TO_RETIRE, invokes_alert=True) sel.handle_alert() flash.assert_success_message( 'Retirement initiated for 1 VM and Instance from the {} Database'. format( version.pick({ version.LOWEST: 'CFME', 'upstream': 'ManageIQ' }))) def power_control_from_provider(self): raise NotImplementedError( "You have to implement power_control_from_provider!") def power_control_from_cfme(self, option, cancel=True, from_details=False): """Power controls a VM from within CFME Args: option: corresponds to option values under the power button cancel: Whether or not to cancel the power operation on confirmation from_details: Whether or not to perform action from instance details page Raises: OptionNotAvailable: option param is not visible or enabled """ if (self.is_pwr_option_available_in_cfme(option=option, from_details=from_details)): pwr_btn(option, invokes_alert=True) sel.handle_alert(cancel=cancel, check_present=True) logger.info( "Power control action of VM/instance %s, option %s, cancel %s executed", self.name, option, str(cancel)) else: raise OptionNotAvailable(option + " is not visible or enabled") def wait_candu_data_available(self, timeout=600): """Waits until C&U data are available for this VM/Instance Args: timeout: Timeout passed to :py:func:`utils.wait.wait_for` """ self.load_details(refresh=True) wait_for(lambda: not toolbar.is_greyed('Monitoring', 'Utilization'), delay=10, handle_exception=True, num_sec=timeout, fail_func=lambda: toolbar.refresh()) def wait_for_vm_state_change(self, desired_state=None, timeout=300, from_details=False, with_relationship_refresh=True, from_any_provider=False): """Wait for M to come to desired state. This function waits just the needed amount of time thanks to wait_for. Args: desired_state: on, off, suspended... for available states, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` timeout: Specify amount of time (in seconds) to wait from_any_provider: Archived/Orphaned vms need this Raises: TimedOutError: When instance does not come up to desired state in specified period of time. InstanceNotFound: When unable to find the instance passed """ detail_t = ("Power Management", "Power State") def _looking_for_state_change(): if from_details: self.load_details(refresh=True) return self.get_detail(properties=detail_t) == desired_state else: return 'currentstate-' + desired_state in self.find_quadicon( from_any_provider=from_any_provider).data['state'] return wait_for( _looking_for_state_change, num_sec=timeout, delay=30, fail_func=lambda: self.refresh_relationships( from_details=from_details, from_any_provider=from_any_provider) if with_relationship_refresh else None) def is_pwr_option_available_in_cfme(self, option, from_details=False): """Checks to see if a power option is available on the VM Args: option: corresponds to option values under the power button, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` from_details: Whether or not to perform action from instance details page """ if from_details: self.load_details(refresh=True) else: entity = self.find_quadicon() entity.check() try: return not toolbar.is_greyed('Power', option) except sel.NoSuchElementException: return False def delete_from_provider(self): logger.info("Begin delete_from_provider") if self.provider.mgmt.does_vm_exist(self.name): try: if self.provider.mgmt.is_vm_suspended( self.name) and self.provider.type != 'azure': logger.debug( "Powering up VM %s to shut it down correctly on %s.", self.name, self.provider.key) self.provider.mgmt.start_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) self.provider.mgmt.stop_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) except exceptions.ActionNotSupported: # Action is not supported on mgmt system. Simply continue pass # One more check (for the suspended one) if self.provider.mgmt.does_vm_exist(self.name): try: logger.info("Mgmt System delete_vm") return self.provider.mgmt.delete_vm(self.name) except exceptions.VMInstanceNotFound: # Does not exist already return True else: return True def create_on_provider(self, timeout=900, find_in_cfme=False, **kwargs): """Create the VM on the provider Args: timeout: Number of seconds to wait for the VM to appear in CFME Will not wait at all, if set to 0 (Defaults to ``900``) """ deploy_template(self.provider.key, self.name, self.template_name, **kwargs) if find_in_cfme: self.provider.refresh_provider_relationships() self.wait_to_appear(timeout=timeout, load_details=False) def does_vm_exist_on_provider(self): """Check if VM exists on provider itself""" return self.provider.mgmt.does_vm_exist(self.name) def set_retirement_date(self, when, warn=None): """Sets the retirement date for this Vm object. It incorporates some magic to make it work reliably since the retirement form is not very pretty and it can't be just "done". Args: when: When to retire. :py:class:`str` in format mm/dd/yyyy of :py:class:`datetime.datetime` or :py:class:`utils.timeutil.parsetime`. warn: When to warn, fills the select in the form in case the ``when`` is specified. """ # TODO: refactor for retirement nav destinations and widget form fill when child classes self.load_details() lcl_btn("Set Retirement Date") if callable(self.retire_form.date_retire): # It is the old functiton sel.wait_for_element("#miq_date_1") else: sel.wait_for_element(self.retire_form.date_retire) if when is None: try: wait_for(lambda: sel.is_displayed(retire_remove_button), num_sec=5, delay=0.2) sel.click(retire_remove_button) wait_for(lambda: not sel.is_displayed(retire_remove_button), num_sec=10, delay=0.2) sel.click(form_buttons.save) except TimedOutError: pass else: if sel.is_displayed(retire_remove_button): sel.click(retire_remove_button) wait_for(lambda: not sel.is_displayed(retire_remove_button), num_sec=15, delay=0.2) fill(self.retire_form.date_retire, when) wait_for(lambda: sel.is_displayed(retire_remove_button), num_sec=15, delay=0.2) if warn is not None: fill(self.retire_form.warn, warn) sel.click(form_buttons.save) def equal_drift_results(self, row_text, section, *indexes): """ Compares drift analysis results of a row specified by it's title text Args: row_text: Title text of the row to compare section: Accordion section where the change happened; this section will be activated indexes: Indexes of results to compare starting with 0 for first row (latest result). Compares all available drifts, if left empty (default). Note: There have to be at least 2 drift results available for this to work. Returns: ``True`` if equal, ``False`` otherwise. """ # mark by indexes or mark all self.load_details(refresh=True) sel.click(InfoBlock("Properties", "Drift History")) if indexes: drift_table.select_rows_by_indexes(*indexes) else: # We can't compare more than 10 drift results at once # so when selecting all, we have to limit it to the latest 10 if len(list(drift_table.rows())) > 10: drift_table.select_rows_by_indexes(*range(0, min(10, len))) else: drift_table.select_all() tb.select("Select up to 10 timestamps for Drift Analysis") # Make sure the section we need is active/open sec_apply_btn = "//div[@id='accordion']/a[contains(normalize-space(text()), 'Apply')]" # Deselect other sections for other_section in drift_section.child_items(): drift_section.check_node(other_section.text) drift_section.uncheck_node(other_section.text) # Activate the required section drift_section.check_node(section) sel.click(sec_apply_btn) if not tb.is_active("All attributes"): tb.select("All attributes") drift_grid = DriftGrid() if any( drift_grid.cell_indicates_change(row_text, i) for i in range(0, len(indexes))): return False return True
from utils.wait import wait_for, TimedOutError from . import PolicyProfileAssignable, Taggable, SummaryMixin access_btn = partial(toolbar.select, "Access") cfg_btn = partial(toolbar.select, "Configuration") lcl_btn = partial(toolbar.select, "Lifecycle") mon_btn = partial(toolbar.select, 'Monitoring') pol_btn = partial(toolbar.select, "Policy") pwr_btn = partial(toolbar.select, "Power") retire_remove_button = "//span[@id='remove_button']/a/img|//a/img[contains(@src, '/clear')]" set_ownership_form = Form(fields=[( 'user_name', AngularSelect('user_name')), ( 'group_name', AngularSelect('group_name')), ( 'create_button', form_buttons.save), ( 'reset_button', form_buttons.reset), ('cancel_button', form_buttons.cancel)]) drift_table = CheckboxTable( "//th[normalize-space(.)='Timestamp']/ancestor::table[1]") drift_section = BootstrapTreeview('all_sectionsbox') def base_types(template=False): from pkg_resources import iter_entry_points search = "template" if template else "vm"
lambda ctx: sel.click(Quadicon(ctx['provider'].name, None)), { 'containers_provider_edit_detail': lambda _: cfg_btn('Edit this Containers Provider'), 'containers_provider_timelines_detail': lambda _: mon_btn('Timelines'), 'containers_provider_edit_tags_detail': lambda _: pol_btn('Edit Tags'), 'containers_provider_topology_detail': lambda _: sel.click(InfoBlock('Overview', 'Topology')) } ] }) properties_form = Form(fields=[( 'type_select', AngularSelect('server_emstype')), ( 'name_text', Input('name')), ('hostname_text', Input('hostname')), ('port_text', Input('port'))]) properties_form_56 = TabStripForm( fields=[('type_select', AngularSelect('ems_type')), ('name_text', Input('name'))], tab_fields={ "Default": [ ('hostname_text', Input("default_hostname")), ('port_text', Input("default_api_port")), ('sec_protocol', AngularSelect("default_security_protocol")), ], "Hawkular": [('hawkular_hostname', Input("hawkular_hostname")), ('hawkular_api_port', Input("hawkular_api_port"))],
('Request', [('email', ui.Input('requester__owner_email')), ('first_name', ui.Input('requester__owner_first_name')), ('last_name', ui.Input('requester__owner_last_name')), ('notes', ui.Input('requester__request_notes')), ('manager_name', ui.Input('requester__owner_manager'))]), ('Purpose', [('apply_tags', { version.LOWEST: ui.CheckboxTree('//div[@id="all_tags_treebox"]//ul'), '5.7': ui.BootstrapTreeview('all_tags_treebox') })]), ( 'Catalog', [ # Cloud ('num_instances', AngularSelect('service__number_of_vms')), ('instance_name', '//input[@name="service__vm_name"]'), ('instance_description', ui.Input('service__vm_description')), # Infra ('vm_filter', AngularSelect('service__vm_filter')), ('num_vms', AngularSelect('service__number_of_vms')), ('vm_name', '//input[@name="service__vm_name"]'), ('vm_description', ui.Input('service__vm_description')), ('catalog_name', ui.Table('//div[@id="prov_vm_div"]/table')), ('provision_type', AngularSelect('service__provision_type')), ('linked_clone', ui.Input('service__linked_clone')), ('pxe_server', AngularSelect('service__pxe_server_id')), ('pxe_image', ui.Table('//div[@id="prov_pxe_img_div"]/table')), ('iso_file', ui.Table('//div[@id="prov_iso_img_div"]/table')) ]),
class BaseVM(Pretty, Updateable, PolicyProfileAssignable, Taggable, SummaryMixin): """Base VM and Template class that holds the largest common functionality between VMs, instances, templates and images. In order to inherit these, you have to implement the ``on_details`` method. """ pretty_attrs = ['name', 'provider', 'template_name'] _registered_types = {} # Forms edit_form = Form( fields=[ ('custom_ident', Input("custom_1")), ('description_tarea', "//textarea[@id='description']"), ('parent_sel', { version.LOWEST: Select("//select[@name='chosen_parent']"), "5.5": AngularSelect("chosen_parent")}), ('child_sel', Select("//select[@id='kids_chosen']", multi=True)), ('vm_sel', Select("//select[@id='choices_chosen']", multi=True)), ('add_btn', "//img[@alt='Move selected VMs to left']"), ('remove_btn', "//img[@alt='Move selected VMs to right']"), ('remove_all_btn', "//img[@alt='Move all VMs to right']"), ]) ### # Factory class methods # @classmethod def register_for_provider_type(cls, *provider_types): """This method is used to assign the subclasses to certain providers. Usage as follows: .. code-block:: python @BaseVM.register_for_provider_type("cloud") class Instance(BaseVM): pass @BaseVM.register_for_provider_type("ec2") class EC2Instance(BaseVM): pass @BaseVM.register_for_provider_type("infra") class VM(BaseVM): pass You can use both the types of providers and also the general classes (infra, cloud). Args: *provider_types: The provider types to assign this class to """ def f(klass): for provider_type in provider_types: if provider_type not in cls._registered_types: cls._registered_types[provider_type] = {} if issubclass(klass, _TemplateMixin): cls._registered_types[provider_type]["template"] = klass else: cls._registered_types[provider_type]["vm"] = klass return klass return f @classmethod def factory(cls, vm_name, provider, template_name=None, template=False): """Factory class method that determines the correct subclass for given provider. For reference how does that work, refer to :py:meth:`register_for_provider_type` Args: vm_name: Name of the VM/Instance as it appears in the UI provider: The provider object (not the string!) template_name: Source template name. Useful when the VM/Instance does not exist and you want to create it. template: Whether the generated object class should be VM/Instance or a template class. """ # Ensure the classes are loaded: import cfme.cloud.instance # NOQA import cfme.infrastructure.virtual_machines # NOQA try: return ( cls._registered_types[provider.type]["template" if template else "vm"] (vm_name, provider, template_name)) except KeyError: # Matching via provider type failed. Maybe we have some generic classes for infra/cloud? try: return ( cls._registered_types[provider.category]["template" if template else "vm"] (vm_name, provider, template_name)) except KeyError: raise UnknownProviderType( 'Unknown type of cloud provider CRUD object: {}' .format(provider.__class__.__name__)) ### # To be set or implemented # ALL_LIST_LOCATION = None TO_OPEN_EDIT = None # Name of the item in Configuration that puts you in the form QUADICON_TYPE = "vm" # Titles of the delete buttons in configuration REMOVE_MULTI = "Remove selected items from the VMDB" # For multiple items REMOVE_SINGLE = "Remove from the VMDB" # For single item ### # Shared behaviour # def __init__(self, name, provider, template_name=None): super(BaseVM, self).__init__() if type(self) in {BaseVM, VM, Template}: raise NotImplementedError('This class cannot be instantiated.') self.name = name self.provider = provider self.template_name = template_name if not hasattr(self, "on_details"): raise NotImplementedError( "You need to implement on_details method in {}!".format(type(self).__name__)) ### # Properties # @property def is_vm(self): return not isinstance(self, _TemplateMixin) @property def quadicon_type(self): return self.QUADICON_TYPE @property def paged_table(self): _paged_table_template = '//div[@id="list_grid"]/div[@class="{}"]/table/tbody' return version.pick({ version.LOWEST: SplitPagedTable(header_data=(_paged_table_template.format("xhdr"), 1), body_data=(_paged_table_template.format("objbox"), 0)), "5.5": PagedTable('//table'), }) ### # Methods # def check_compliance(self): """Clicks the Check compliance button.""" self.load_details(refresh=True) pol_btn("Check Compliance of Last Known Configuration", invokes_alert=True) sel.handle_alert() flash.assert_no_errors() @contextmanager def check_compliance_wrapper(self, timeout=240): """This wrapper takes care of waiting for the compliance status to change Args: timeout: Wait timeout in seconds. """ self.load_details(refresh=True) original_state = self.compliance_status yield wait_for( lambda: self.compliance_status != original_state, num_sec=timeout, delay=5, message="compliance of {} checked".format(self.name), fail_func=lambda: toolbar.select("Reload")) def check_compliance_and_wait(self, timeout=240): """Initiates compliance check and waits for it to finish.""" with self.check_compliance_wrapper(timeout=timeout): self.check_compliance() return self.compliant @property def compliance_status(self): """Returns the title of the compliance infoblock. The title contains datetime so it can be compared. Returns: :py:class:`NoneType` if no title is present (no compliance checks before), otherwise str """ self.load_details(refresh=True) return InfoBlock("Compliance", "Status").title @property def compliant(self): """Check if the VM is compliant Returns: :py:class:`NoneType` if the VM was never verified, otherwise :py:class:`bool` """ text = self.get_detail(properties=("Compliance", "Status")).strip().lower() if text == "never verified": return None elif text.startswith("non-compliant"): return False elif text.startswith("compliant"): return True else: raise ValueError("{} is not a known state for compliance".format(text)) def delete(self, cancel=False, from_details=False): """Deletes the VM/Instance from the VMDB. Args: cancel: Whether to cancel the action in the alert. from_details: Whether to use the details view or list view. """ if from_details: self.load_details(refresh=True) cfg_btn(self.REMOVE_SINGLE, invokes_alert=True) else: self.find_quadicon(mark=True) cfg_btn(self.REMOVE_MULTI, invokes_alert=True) sel.handle_alert(cancel=cancel) @property def exists(self): """Checks presence of the quadicon in the CFME.""" try: self.find_quadicon() return True except VmOrInstanceNotFound: return False def find_quadicon( self, do_not_navigate=False, mark=False, refresh=True, from_any_provider=False, use_search=True): """Find and return a quadicon belonging to a specific vm Args: from_any_provider: Whether to look for it anywhere (root of the tree). Useful when looking up archived or orphaned VMs Returns: :py:class:`cfme.web_ui.Quadicon` instance Raises: VmOrInstanceNotFound """ quadicon = Quadicon(self.name, self.quadicon_type) if not do_not_navigate: if from_any_provider: sel.force_navigate(self.ALL_LIST_LOCATION) elif self.is_vm: self.provider.load_all_provider_vms() else: self.provider.load_all_provider_templates() toolbar.select('Grid View') else: # Search requires navigation, we shouldn't use it then use_search = False if refresh: sel.refresh() if not paginator.page_controls_exist(): if self.is_vm: raise VmOrInstanceNotFound("VM '{}' not found in UI!".format(self.name)) else: raise TemplateNotFound("Template '{}' not found in UI!".format(self.name)) # this is causing some issues in 5.5.0.9, commenting out for a bit # paginator.results_per_page(1000) if use_search: try: if not search.has_quick_search_box(): # We don't use provider-specific page (vm_templates_provider_branch) here # as those don't list archived/orphaned VMs if self.is_vm: sel.force_navigate(self.provider.instances_page_name) else: sel.force_navigate(self.provider.templates_page_name) search.normal_search(self.name) except Exception as e: logger.warning("Failed to use search: %s", str(e)) for page in paginator.pages(): if sel.is_displayed(quadicon, move_to=True): if mark: sel.check(quadicon.checkbox()) return quadicon else: raise VmOrInstanceNotFound("VM '{}' not found in UI!".format(self.name)) def get_detail(self, properties=None, icon_href=False): """Gets details from the details infoblock The function first ensures that we are on the detail page for the specific VM/Instance. Args: properties: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Returns: A string representing the contents of the InfoBlock's value. """ self.load_details(refresh=True) if icon_href: return InfoBlock.icon_href(*properties) else: return InfoBlock.text(*properties) def open_details(self, properties=None): """Clicks on details infoblock""" self.load_details(refresh=True) sel.click(InfoBlock(*properties)) @classmethod def get_first_vm_title(cls, do_not_navigate=False, provider=None): """Get the title of first VM/Instance.""" if not do_not_navigate: if provider is None: sel.force_navigate(cls.ALL_LIST_LOCATION) else: provider.load_all_provider_vms() return Quadicon.get_first_quad_title() @property def last_analysed(self): """Returns the contents of the ``Last Analysed`` field in summary""" return self.get_detail(properties=('Lifecycle', 'Last Analyzed')).strip() def load_details(self, refresh=False): """Navigates to an VM's details page. Args: refresh: Refreshes the VM page if already there Raises: VmOrInstanceNotFound: When unable to find the VM passed """ if not self.on_details(): logger.debug("load_details: not on details already") sel.click(self.find_quadicon()) else: if refresh: toolbar.refresh() def open_edit(self): """Loads up the edit page of the object.""" self.load_details(refresh=True) cfg_btn(self.TO_OPEN_EDIT) def open_timelines(self): self.load_details(refresh=True) mon_btn("Timelines") def rediscover(self): """Deletes the VM from the provider and lets it discover again""" self.delete(from_details=True) self.wait_for_delete() self.provider.refresh_provider_relationships() self.wait_to_appear() def rediscover_if_analysis_data_present(self): """Rediscovers the object if it has some analysis data present. Returns: Boolean if the rediscovery happened. """ if self.last_analysed.lower() != 'never': self.rediscover() return True return False def refresh_relationships(self, from_details=False, cancel=False): """Executes a refresh of relationships. Args: from_details: Whether or not to perform action from instance details page cancel: Whether or not to cancel the refresh relationships action """ if from_details: self.load_details() else: self.find_quadicon(mark=True) cfg_btn('Refresh Relationships and Power States', invokes_alert=True) sel.handle_alert(cancel=cancel) @property def retirement_date(self): """Returns the retirement date of the selected machine. Returns: :py:class:`NoneType` if there is none, or :py:class:`utils.timeutil.parsetime` """ date_str = self.get_detail(properties=("Lifecycle", "Retirement Date")).strip() if date_str.lower() == "never": return None return parsetime.from_american_date_only(date_str) def smartstate_scan(self, cancel=False, from_details=False): """Initiates fleecing from the UI. Args: cancel: Whether or not to cancel the refresh relationships action from_details: Whether or not to perform action from instance details page """ if from_details: self.load_details(refresh=True) else: self.find_quadicon(mark=True) cfg_btn('Perform SmartState Analysis', invokes_alert=True) sel.handle_alert(cancel=cancel) def wait_to_disappear(self, timeout=600, load_details=True): """Wait for a VM to disappear within CFME Args: timeout: time (in seconds) to wait for it to appear """ wait_for( lambda: self.exists, num_sec=timeout, delay=30, fail_func=sel.refresh, fail_condition=True) wait_for_delete = wait_to_disappear # An alias for more fitting verbosity def wait_to_appear(self, timeout=600, load_details=True): """Wait for a VM to appear within CFME Args: timeout: time (in seconds) to wait for it to appear from_details: when found, should it load the vm details """ wait_for(lambda: self.exists, num_sec=timeout, delay=30, fail_func=sel.refresh) if load_details: self.load_details() def set_ownership(self, user=None, group=None, click_cancel=False, click_reset=False): """Set ownership of the VM/Instance or Template/Image""" sel.click(self.find_quadicon(False, False, False)) cfg_btn('Set Ownership') if click_reset: action = form_buttons.reset msg_assert = lambda: flash.assert_message_match( 'All changes have been reset') elif click_cancel: action = form_buttons.cancel msg_assert = lambda: flash.assert_success_message( 'Set Ownership was cancelled by the user') else: action = form_buttons.save msg_assert = lambda: flash.assert_success_message( 'Ownership saved for selected {}'.format(self.VM_TYPE)) fill(set_ownership_form, {'user_name': user, 'group_name': group}, action=action) msg_assert() def unset_ownership(self): """Unset ownership of the VM/Instance or Template/Image""" # choose the vm code comes here sel.click(self.find_quadicon(False, False, False)) cfg_btn('Set Ownership') fill(set_ownership_form, {'user_name': '<No Owner>', 'group_name': 'EvmGroup-administrator'}, action=form_buttons.save) flash.assert_success_message('Ownership saved for selected {}'.format(self.VM_TYPE))
from utils.pretty import Pretty from utils.varmeth import variable from utils.log import logger from utils.wait import wait_for paged_tbl = PagedTable(table_locator="//div[@id='list_grid']//table") cfg_btn = partial(tb.select, 'Configuration') mon_btn = partial(tb.select, 'Monitoring') pol_btn = partial(tb.select, 'Policy') details_page = Region(infoblock_type='detail') properties_form = Form(fields=[( 'type_select', AngularSelect('server_emstype')), ( 'name_text', Input('name')), ('hostname_text', Input('hostname')), ('port_text', Input('port'))]) properties_form_56 = TabStripForm( fields=[('type_select', AngularSelect('ems_type')), ('name_text', Input('name'))], tab_fields={ "Default": [ ('hostname_text', Input("default_hostname")), ('port_text', Input("default_api_port")), ('sec_protocol', AngularSelect("default_security_protocol", exact=True)), ], "Hawkular": [('hawkular_hostname', Input("hawkular_hostname")),
# Page specific locators details_page = Region(infoblock_type='detail') page_title_loc = '//div[@id="center_div" or @id="main-content"]//h1' properties_form = Form(fields=[ ('name_text', Input("name")), ('hostname_text', Input("hostname")), ('ipaddress_text', Input("ipaddress"), { "removed_since": "5.4.0.0.15" }), ('custom_ident_text', Input("custom")), ('host_platform', { version.LOWEST: Select('//select[@id="user_assigned_os"]'), '5.5': AngularSelect('user_assigned_os') }), ('ipmi_address_text', Input("ipmi_address")), ('mac_address_text', Input("mac_address")), ]) credential_form = Form(fields=[ ('default_button', "//div[@id='auth_tabs']/ul/li/a[@href='#default']"), ('default_principal', Input("default_userid")), ('default_secret', Input("default_password")), ('default_verify_secret', Input("default_verify")), ('ipmi_button', "//div[@id='auth_tabs']/ul/li/a[@href='#ipmi']"), ('ipmi_principal', Input("ipmi_userid")), ('ipmi_secret', Input("ipmi_password")), ('ipmi_verify_secret', Input("ipmi_verify")), ('validate_btn', form_buttons.validate),
class Group(Updateable, Pretty): group_form = Form( fields=[('description_txt', Input('description')), ('role_select', { version.LOWEST: Select("//*[@id='group_role']"), "5.5": AngularSelect("group_role") }), ('group_tenant', AngularSelect("group_tenant"), { "appeared_in": "5.5" })]) pretty_attrs = ['description', 'role'] def __init__(self, description=None, role=None, tenant="My Company"): self.description = description self.role = role self.tenant = tenant def create(self): sel.force_navigate('cfg_accesscontrol_group_add') fill(self.group_form, { 'description_txt': self.description, 'role_select': self.role, 'group_tenant': self.tenant }, action=form_buttons.add) flash.assert_success_message('Group "{}" was saved'.format( self.description)) def update(self, updates): sel.force_navigate("cfg_accesscontrol_group_edit", context={"group": self}) fill(self.group_form, { 'description_txt': updates.get('description'), 'role_select': updates.get('role'), 'group_tenant': updates.get('tenant') }, action=form_buttons.save) flash.assert_success_message('Group "{}" was saved'.format( updates.get('description', self.description))) def delete(self): sel.force_navigate("cfg_accesscontrol_group_ed", context={"group": self}) tb_select('Delete this Group', invokes_alert=True) sel.handle_alert() flash.assert_success_message( 'EVM Group "{}": Delete successful'.format(self.description)) def edit_tags(self, tag, value): sel.force_navigate("cfg_accesscontrol_group_ed", context={"group": self}) pol_btn("Edit 'My Company' Tags for this Group", invokes_alert=True) fill(edit_tags_form, { 'select_tag': tag, 'select_value': value }, action=form_buttons.save) flash.assert_success_message('Tag edits were successfully saved') def remove_tag(self, tag, value): sel.force_navigate("cfg_accesscontrol_group_ed", context={"group": self}) pol_btn("Edit 'My Company' Tags for this Group", invokes_alert=True) row = tag_table.find_row_by_cells( { 'category': tag, 'assigned_value': value }, partial_check=True) sel.click(row[0]) form_buttons.save() flash.assert_success_message('Tag edits were successfully saved')
update_buttons = Region( locators={ 'edit_registration': make_update_button("Edit Registration"), 'refresh': make_update_button("Refresh List"), 'check_updates': make_update_button("Check for Updates"), 'register': make_update_button("Register"), 'apply_updates': make_update_button("Apply CFME Update") }, identifying_loc="edit_registration" ) registration_form = Form( fields=[ ("service", AngularSelect('register_to')), ("url", Input('server_url')), ("repo_name", Input('repo_name')), ("use_proxy", Input('use_proxy')), ("proxy_url", Input('proxy_address')), ("proxy_username", Input('proxy_userid')), ("proxy_password", Input('proxy_password')), ("proxy_password_verify", Input('proxy_password2')), # 5.4+ ("username", Input('customer_userid')), ("password", Input('customer_password')), ("password_verify", Input('customer_password2')), # 5.4+ ] ) registration_buttons = Region(