class CloudProvider(Pretty, CloudInfraProvider): """ Abstract model of a cloud provider in cfme. See EC2Provider or OpenStackProvider. Args: name: Name of the provider. details: a details record (see EC2Details, OpenStackDetails inner class). credentials (:py:class:`Credential`): see Credential class. key: The CFME key of the provider in the yaml. Usage: myprov = EC2Provider(name='foo', region='us-west-1', credentials=Credential(principal='admin', secret='foobar')) myprov.create() """ provider_types = {} in_version = (version.LOWEST, version.LATEST) category = "cloud" pretty_attrs = ['name', 'credentials', 'zone', 'key'] STATS_TO_MATCH = ['num_template', 'num_vm'] string_name = "Cloud" page_name = "clouds" templates_destination_name = "Images" quad_name = "cloud_prov" vm_name = "Instances" template_name = "Images" _properties_region = prop_region # This will get resolved in common to a real form db_types = ["CloudManager"] # Specific Add button add_provider_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Add this Cloud Provider"), '5.5': form_buttons.add }) save_button = deferred_verpick({ version.LOWEST: form_buttons.save, '5.5': form_buttons.angular_save }) def __init__(self, name=None, credentials=None, zone=None, key=None, appliance=None): Navigatable.__init__(self, appliance=appliance) if not credentials: credentials = {} self.name = name self.credentials = credentials self.zone = zone self.key = key def _form_mapping(self, create=None, **kwargs): return {'name_text': kwargs.get('name')}
class ContainerImageCondition(BaseCondition): TREE_NODE = "Container Image" PRETTY = deferred_verpick({ version.LOWEST: "Image", '5.7': "Container Image", }) FIELD_VALUE = deferred_verpick({ version.LOWEST: "Image", '5.7': "Container Image", })
class Provider(Pretty, CloudInfraProvider): """ Abstract model of a cloud provider in cfme. See EC2Provider or OpenStackProvider. Args: name: Name of the provider. details: a details record (see EC2Details, OpenStackDetails inner class). credentials (Credential): see Credential inner class. key: The CFME key of the provider in the yaml. Usage: myprov = EC2Provider(name='foo', region='us-west-1', credentials=Provider.Credential(principal='admin', secret='foobar')) myprov.create() """ pretty_attrs = ['name', 'credentials', 'zone', 'key'] STATS_TO_MATCH = ['num_template', 'num_vm'] string_name = "Cloud" page_name = "clouds" quad_name = "cloud_prov" vm_name = "Instances" template_name = "Images" properties_form = properties_form # Specific Add button add_provider_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Add this Cloud Provider"), '5.5': form_buttons.FormButton("Add") }) save_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Save Changes"), '5.5': form_buttons.FormButton("Save changes") }) def __init__(self, name=None, credentials=None, zone=None, key=None): if not credentials: credentials = {} self.name = name self.credentials = credentials self.zone = zone self.key = key def _form_mapping(self, create=None, **kwargs): return {'name_text': kwargs.get('name')}
class ProviderCondition(BaseCondition): TREE_NODE = deferred_verpick({ version.LOWEST: "Container Provider", '5.7.2': "Provider", }) PRETTY = "Provider" FIELD_VALUE = "Provider"
class Provider(BaseProvider, Pretty): pretty_attrs = ['name', 'key', 'zone'] STATS_TO_MATCH = [ 'num_project', 'num_service', 'num_replication_controller', 'num_pod', 'num_node', 'num_container', 'num_image' ] # TODO add 'num_image_registry' and 'num_volume' string_name = "Containers" page_name = "containers" detail_page_suffix = 'provider_detail' edit_page_suffix = 'provider_edit_detail' refresh_text = "Refresh items and relationships" quad_name = None _properties_region = prop_region # This will get resolved in common to a real form add_provider_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Add this Containers Provider"), '5.6': form_buttons.add }) save_button = deferred_verpick({ version.LOWEST: form_buttons.save, '5.6': form_buttons.angular_save }) def __init__(self, name=None, credentials=None, key=None, zone=None, hostname=None, port=None, provider_data=None): if not credentials: credentials = {} self.name = name self.credentials = credentials self.key = key self.zone = zone self.hostname = hostname self.port = port self.provider_data = provider_data def _on_detail_page(self): """ Returns ``True`` if on the providers detail page, ``False`` if not.""" ensure_browser_open() return sel.is_displayed( '//div//h1[contains(., "{} (Summary)")]'.format(self.name)) def load_details(self, refresh=False): if not self._on_detail_page(): self.navigate(detail=True) elif refresh: tb.refresh() def navigate(self, detail=True): if detail is True: if not self._on_detail_page(): sel.force_navigate('containers_provider_detail', context={'provider': self}) else: sel.force_navigate('containers_provider', context={'provider': self}) def get_detail(self, *ident): """ Gets details from the details infoblock Args: *ident: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Returns: A string representing the contents of the InfoBlock's value. """ self.navigate(detail=True) return details_page.infoblock.text(*ident) @variable(alias='db') def num_project(self): return self._num_db_generic('container_projects') @num_project.variant('ui') def num_project_ui(self): return int(self.get_detail("Relationships", "Projects")) @variable(alias='db') def num_service(self): return self._num_db_generic('container_services') @num_service.variant('ui') def num_service_ui(self): return int(self.get_detail("Relationships", "Services")) @variable(alias='db') def num_replication_controller(self): return self._num_db_generic('container_replicators') @num_replication_controller.variant('ui') def num_replication_controller_ui(self): return int(self.get_detail("Relationships", "Replicators")) @variable(alias='db') def num_container_group(self): return self._num_db_generic('container_groups') @num_container_group.variant('ui') def num_container_group_ui(self): return int(self.get_detail("Relationships", "Pods")) @variable(alias='db') def num_pod(self): # potato tomato return self.num_container_group() @num_pod.variant('ui') def num_pod_ui(self): # potato tomato return self.num_container_group(method='ui') @variable(alias='db') def num_node(self): return self._num_db_generic('container_nodes') @num_node.variant('ui') def num_node_ui(self): return int(self.get_detail("Relationships", "Nodes")) @variable(alias='db') def num_container(self): # Containers are linked to providers through container definitions and then through pods res = cfmedb().engine.execute( "SELECT count(*) " "FROM ext_management_systems, container_groups, container_definitions, containers " "WHERE containers.container_definition_id=container_definitions.id " "AND container_definitions.container_group_id=container_groups.id " "AND container_groups.ems_id=ext_management_systems.id " "AND ext_management_systems.name='{}'".format(self.name)) return int(res.first()[0]) @num_container.variant('ui') def num_container_ui(self): return int(self.get_detail("Relationships", "Containers")) @variable(alias='db') def num_image(self): return self._num_db_generic('container_images') @num_image.variant('ui') def num_image_ui(self): return int(self.get_detail("Relationships", "Images")) @variable(alias='db') def num_image_registry(self): return self._num_db_generic('container_image_registries') @num_image_registry.variant('ui') def num_image_registry_ui(self): return int(self.get_detail("Relationships", "Image Registries"))
class ContainersProvider(BaseProvider, Pretty): PLURAL = 'Providers' provider_types = {} in_version = ('5.5', version.LATEST) category = "container" pretty_attrs = ['name', 'key', 'zone'] STATS_TO_MATCH = [ 'num_project', 'num_service', 'num_replication_controller', 'num_pod', 'num_node', 'num_image_registry', 'num_container' ] # TODO add 'num_volume' string_name = "Containers" page_name = "containers" detail_page_suffix = 'provider_detail' edit_page_suffix = 'provider_edit_detail' refresh_text = "Refresh items and relationships" quad_name = None db_types = ["ContainerManager"] _properties_region = prop_region # This will get resolved in common to a real form add_provider_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Add this Containers Provider"), '5.6': form_buttons.add }) save_button = deferred_verpick({ version.LOWEST: form_buttons.save, '5.6': form_buttons.angular_save }) def __init__(self, name=None, credentials=None, key=None, zone=None, hawkular=None, hostname=None, api_port=None, sec_protocol=None, hawkular_sec_protocol=None, hawkular_hostname=None, hawkular_api_port=None, provider_data=None, appliance=None): Navigatable.__init__(self, appliance=appliance) if not credentials: credentials = {} self.name = name self.credentials = credentials self.key = key self.zone = zone self.hawkular = hawkular self.hostname = hostname self.api_port = api_port self.sec_protocol = sec_protocol self.hawkular_sec_protocol = hawkular_sec_protocol self.hawkular_hostname = hawkular_hostname self.hawkular_api_port = hawkular_api_port self.provider_data = provider_data def _on_detail_page(self): """ Returns ``True`` if on the providers detail page, ``False`` if not.""" ensure_browser_open() return sel.is_displayed( '//div//h1[contains(., "{} (Summary)")]'.format(self.name)) def load_details(self, refresh=False): navigate_to(self, 'Details') if refresh: tb.refresh() def get_detail(self, *ident): """ Gets details from the details infoblock Args: *ident: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Returns: A string representing the contents of the InfoBlock's value. """ navigate_to(self, 'Details') return details_page.infoblock.text(*ident) @variable(alias='db') def num_project(self): return self._num_db_generic('container_projects') @num_project.variant('ui') def num_project_ui(self): return int(self.get_detail("Relationships", "Projects")) @variable(alias='db') def num_service(self): return self._num_db_generic('container_services') @num_service.variant('ui') def num_service_ui(self): if self.appliance.version < "5.7": name = "Services" else: name = "Container Services" return int(self.get_detail("Relationships", name)) @variable(alias='db') def num_replication_controller(self): return self._num_db_generic('container_replicators') @num_replication_controller.variant('ui') def num_replication_controller_ui(self): return int(self.get_detail("Relationships", "Replicators")) @variable(alias='db') def num_container_group(self): return self._num_db_generic('container_groups') @num_container_group.variant('ui') def num_container_group_ui(self): return int(self.get_detail("Relationships", "Pods")) @variable(alias='db') def num_pod(self): # potato tomato return self.num_container_group() @num_pod.variant('ui') def num_pod_ui(self): # potato tomato return self.num_container_group(method='ui') @variable(alias='db') def num_node(self): return self._num_db_generic('container_nodes') @num_node.variant('ui') def num_node_ui(self): return int(self.get_detail("Relationships", "Nodes")) @variable(alias='db') def num_container(self): # Containers are linked to providers through container definitions and then through pods res = self.appliance.db.client.engine.execute( "SELECT count(*) " "FROM ext_management_systems, container_groups, container_definitions, containers " "WHERE containers.container_definition_id=container_definitions.id " "AND container_definitions.container_group_id=container_groups.id " "AND container_groups.ems_id=ext_management_systems.id " "AND ext_management_systems.name='{}'".format(self.name)) return int(res.first()[0]) @num_container.variant('ui') def num_container_ui(self): return int(self.get_detail("Relationships", "Containers")) @variable(alias='db') def num_image(self): return self._num_db_generic('container_images') @num_image.variant('ui') def num_image_ui(self): if self.appliance.version < "5.7": name = "Images" else: name = "Container Images" return int(self.get_detail("Relationships", name)) @variable(alias='db') def num_image_registry(self): return self._num_db_generic('container_image_registries') @num_image_registry.variant('ui') def num_image_registry_ui(self): return int(self.get_detail("Relationships", "Image Registries")) def pods_per_ready_status(self): """Grabing the Container Statuses Summary of the pods from API""" # TODO: Add later this logic to wrapanapi entities = self.mgmt.api.get('pod')[1]['items'] out = {} for entity_j in entities: out[entity_j['metadata']['name']] = { condition['type']: eval_strings([condition['status']]).pop() for condition in entity_j['status'].get('conditions', []) } return out
class FolderManager(Pretty): """Class used in Reports/Edit Reports menus.""" _fields = deferred_verpick({ version.LOWEST: ".//div[@id='folder_grid']/div[contains(@class, 'objbox')]/table/tbody/tr/td", "5.5.0.7": ".//div[@id='folder_grid']/ul/li" }) _field = deferred_verpick({ version.LOWEST: ".//div[@id='folder_grid']/div[contains(@class, 'objbox')]/table/tbody/tr" "/td[normalize-space(.)='{}']", "5.5.0.7": ".//div[@id='folder_grid']/ul/li[normalize-space(.)='{}']" }) pretty_attrs = ['root'] # Keep the number of items in versions the same as buttons' and actions' values! # If a new version arrives, extend all the tuples :) versions = (version.LOWEST, "5.5.0.7") actions = ("_click_button", "_click_button_i") buttons = { "move_top": ("Move selected folder top", "fa-angle-double-up"), "move_bottom": ("Move selected folder to bottom", "fa-angle-double-down"), "move_up": ("Move selected folder up", "fa-angle-up"), "move_down": ("Move selected folder down", "fa-angle-down"), "delete_folder": ("Delete selected folder and its contents", "fa-times"), "add_subfolder": ("Add subfolder to selected folder", "fa-plus"), } class _BailOut(Exception): pass def __init__(self, root): self.root = lambda: sel.element(root) def __repr__(self): return "{}({})".format(type(self).__name__, str(repr(self.root))) @classmethod def bail_out(cls): """If something gets wrong, you can use this method to cancel editing of the items in the context manager. Raises: :py:class:`FolderManager._BailOut` exception """ raise cls._BailOut() def _click_button(self, alt): sel.click(sel.element(".//img[@alt='{}']".format(alt), root=self.root)) def _click_button_i(self, klass): sel.click(sel.element("i.{}".format(klass), root=self.root)) def __getattr__(self, attr): """Resulve the button clicking action.""" try: a_tuple = self.buttons[attr] except KeyError: raise AttributeError("Action {} does not exist".format(attr)) action = version.pick(dict(zip(self.versions, self.actions))) action_meth = getattr(self, action) action_data = version.pick(dict(zip(self.versions, a_tuple))) def _click_function(): action_meth(action_data) return _click_function def commit(self): self._click_button("Commit expression element changes") def discard(self): self._click_button("Discard expression element changes") @property def _all_fields(self): return sel.elements(self._fields, root=self.root) @property def fields(self): """Returns all fields' text values""" return map(lambda el: sel.text(el).encode("utf-8"), self._all_fields) @property def selected_field_element(self): """Return selected field's element. Returns: :py:class:`WebElement` if field is selected, else `None` """ if version.current_version() < "5.5.0.7": active = "cellselected" else: active = "active" selected_fields = filter( lambda el: active in sel.get_attribute(el, "class"), self._all_fields) if len(selected_fields) == 0: return None else: return selected_fields[0] @property def selected_field(self): """Return selected field's text. Returns: :py:class:`str` if field is selected, else `None` """ sf = self.selected_field_element return None if sf is None else sel.text(sf).encode("utf-8").strip() def add(self, subfolder): self.add_subfolder() wait_for(lambda: self.selected_field_element is not None, num_sec=5, delay=0.1) sel.double_click(self.selected_field_element, wait_ajax=False) if version.current_version() < "5.5.0.7": input = wait_for(lambda: sel.elements( "./input", root=self.selected_field_element), num_sec=5, delay=0.1, fail_condition=[])[0][0] sel.set_text(input, subfolder) sel.send_keys(input, Keys.RETURN) else: sel.handle_alert(prompt=subfolder) def select_field(self, field): """Select field by text. Args: field: Field text. """ sel.click(sel.element(self._field.format(field), root=self.root)) wait_for(lambda: self.selected_field is not None, num_sec=5, delay=0.1) def has_field(self, field): """Returns if the field is present. Args: field: Field to check. """ try: self.select_field(field) return True except (NoSuchElementException, TimedOutError): return False def delete_field(self, field): self.select_field(field) self.delete_folder() def move_first(self, field): self.select_field(field) self.move_top() def move_last(self, field): self.select_field(field) self.move_bottom() def clear(self): for field in self.fields: self.delete_field(field)
class PolicyProfileAssignable(object): """This class can be inherited by anything that provider load_details method. It provides functionality to assign and unassign Policy Profiles""" manage_policies_tree = deferred_verpick({ version.LOWEST: CheckboxTree("//div[@id='protect_treebox']/ul"), "5.7": BootstrapTreeview("protectbox") }) @property def assigned_policy_profiles(self): try: return self._assigned_policy_profiles except AttributeError: self._assigned_policy_profiles = set([]) return self._assigned_policy_profiles def assign_policy_profiles(self, *policy_profile_names): """ Assign Policy Profiles to this object. Args: policy_profile_names: :py:class:`str` with Policy Profile names. After Control/Explorer coverage goes in, PolicyProfile objects will be also passable. """ map(self.assigned_policy_profiles.add, policy_profile_names) self._assign_unassign_policy_profiles(True, *policy_profile_names) def unassign_policy_profiles(self, *policy_profile_names): """ Unssign Policy Profiles to this object. Args: policy_profile_names: :py:class:`str` with Policy Profile names. After Control/Explorer coverage goes in, PolicyProfile objects will be also passable. """ for pp_name in policy_profile_names: try: self.assigned_policy_profiles.remove(pp_name) except KeyError: pass self._assign_unassign_policy_profiles(False, *policy_profile_names) def _assign_unassign_policy_profiles(self, assign, *policy_profile_names): """DRY function for managing policy profiles. See :py:func:`assign_policy_profiles` and :py:func:`assign_policy_profiles` Args: assign: Wheter to assign or unassign. policy_profile_names: :py:class:`str` with Policy Profile names. """ self.load_details(refresh=True) pol_btn("Manage Policies") for policy_profile in policy_profile_names: if assign: self.manage_policies_tree.check_node(policy_profile) else: self.manage_policies_tree.uncheck_node(policy_profile) form_buttons.save() flash.assert_no_errors()
class Host(Updateable, Pretty, Navigatable, PolicyProfileAssignable): """ Model of an infrastructure host in cfme. Args: name: Name of the host. hostname: Hostname of the host. ip_address: The IP address as a string. custom_ident: The custom identifiter. host_platform: Included but appears unused in CFME at the moment. ipmi_address: The IPMI address. mac_address: The mac address of the system. credentials (:py:class:`Credential`): see Credential inner class. ipmi_credentials (:py:class:`Credential`): see Credential inner class. Usage: myhost = Host(name='vmware', credentials=Provider.Credential(principal='admin', secret='foobar')) myhost.create() """ pretty_attrs = ['name', 'hostname', 'ip_address', 'custom_ident'] forced_saved = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Save Changes", dimmed_alt="Save", force_click=True), '5.5': form_buttons.FormButton("Save changes", dimmed_alt="Save changes", force_click=True) }) def __init__(self, name=None, hostname=None, ip_address=None, custom_ident=None, host_platform=None, ipmi_address=None, mac_address=None, credentials=None, ipmi_credentials=None, interface_type='lan', provider=None, appliance=None): Navigatable.__init__(self, appliance=appliance) self.name = name self.quad_name = 'host' self.hostname = hostname self.ip_address = ip_address self.custom_ident = custom_ident self.host_platform = host_platform self.ipmi_address = ipmi_address self.mac_address = mac_address self.credentials = credentials self.ipmi_credentials = ipmi_credentials self.interface_type = interface_type self.db_id = None self.provider = provider def _form_mapping(self, create=None, **kwargs): return { 'name_text': kwargs.get('name'), 'hostname_text': kwargs.get('hostname'), 'ipaddress_text': kwargs.get('ip_address'), 'custom_ident_text': kwargs.get('custom_ident'), 'host_platform': kwargs.get('host_platform'), 'ipmi_address_text': kwargs.get('ipmi_address'), 'mac_address_text': kwargs.get('mac_address') } class Credential(cfme.Credential, Updateable): """Provider credentials Args: **kwargs: If using IPMI type credential, ipmi = True""" def __init__(self, **kwargs): super(Host.Credential, self).__init__(**kwargs) self.ipmi = kwargs.get('ipmi') def _submit(self, cancel, submit_button): if cancel: sel.click(form_buttons.cancel) # sel.wait_for_element(page.configuration_btn) else: sel.click(submit_button) flash.assert_no_errors() def create(self, cancel=False, validate_credentials=False): """ Creates a host in the UI Args: cancel (boolean): Whether to cancel out of the creation. The cancel is done after all the information present in the Host has been filled in the UI. validate_credentials (boolean): Whether to validate credentials - if True and the credentials are invalid, an error will be raised. """ navigate_to(self, 'Add') fill(properties_form, self._form_mapping(True, **self.__dict__)) fill(credential_form, self.credentials, validate=validate_credentials) fill(credential_form, self.ipmi_credentials, validate=validate_credentials) self._submit(cancel, host_add_btn) def update(self, updates, cancel=False, validate_credentials=False): """ Updates a host in the UI. Better to use utils.update.update context manager than call this directly. Args: updates (dict): fields that are changing. cancel (boolean): whether to cancel out of the update. """ navigate_to(self, 'Edit') change_stored_password() fill(credential_form, updates.get('credentials', None), validate=validate_credentials) logger.debug("Trying to save update for host with id: " + str(self.get_db_id)) self._submit(cancel, self.forced_saved) def delete(self, cancel=True): """ Deletes a host from CFME Args: cancel: Whether to cancel the deletion, defaults to True """ navigate_to(self, 'Details') if self.appliance.version >= '5.7': btn_name = "Remove item" else: btn_name = "Remove from the VMDB" cfg_btn(btn_name, invokes_alert=True) sel.handle_alert(cancel=cancel) def load_details(self, refresh=False): """To be compatible with the Taggable and PolicyProfileAssignable mixins.""" navigate_to(self, 'Details') if refresh: sel.refresh() def execute_button(self, button_group, button, cancel=True): navigate_to(self, 'Details') host_btn = partial(tb.select, button_group) host_btn(button, invokes_alert=True) sel.click(form_buttons.submit) flash.assert_success_message("Order Request was Submitted") host_btn(button, invokes_alert=True) sel.click(form_buttons.cancel) flash.assert_success_message("Service Order was cancelled by the user") def power_on(self): navigate_to(self, 'Details') pow_btn('Power On', invokes_alert=True) sel.handle_alert() def power_off(self): navigate_to(self, 'Details') pow_btn('Power Off', invokes_alert=True) sel.handle_alert() def get_power_state(self): return self.get_detail('Properties', 'Power State') # return str(find_quadicon(self.name, do_not_navigate=True).state) # return state.split()[1] def refresh(self, cancel=False): tb.select("Configuration", "Refresh Relationships and Power States", invokes_alert=True) sel.handle_alert(cancel=cancel) # TODO remove provider_crud when issue #4137 fixed,host linked with provider def wait_for_host_state_change(self, desired_state, timeout=300, provider_crud=None): """Wait for Host to come to desired state. This function waits just the needed amount of time thanks to wait_for. Args: self: self desired_state: 'on' or 'off' timeout: Specify amount of time (in seconds) to wait until TimedOutError is raised provider_crud: provider object where vm resides on (optional) """ def _looking_for_state_change(): tb.refresh() return 'currentstate-' + desired_state in find_quadicon( self.name, do_not_navigate=False).state navigate_and_select_all_hosts(self.name, provider_crud) return wait_for(_looking_for_state_change, num_sec=timeout) def get_ipmi(self): return IPMI(hostname=self.ipmi_address, username=self.ipmi_credentials.principal, password=self.ipmi_credentials.secret, interface_type=self.interface_type) def get_detail(self, *ident): """ Gets details from the details infoblock The function first ensures that we are on the detail page for the specific host. Args: *ident: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Returns: A string representing the contents of the InfoBlock's value. """ navigate_to(self, 'Details') return details_page.infoblock.text(*ident) @property def exists(self): navigate_to(self, 'All') for page in paginator.pages(): if sel.is_displayed(Quadicon(self.name, 'host')): return True else: return False @property def has_valid_credentials(self): """ Check if host has valid credentials saved Returns: ``True`` if credentials are saved and valid; ``False`` otherwise """ navigate_to(self, 'All') quad = Quadicon(self.name, 'host') return 'checkmark' in quad.creds def get_datastores(self): """ Gets list of all datastores used by this host""" navigate_to(self, 'Details') list_acc.select('Relationships', 'Datastores', by_title=False, partial=True) return [q.name for q in Quadicon.all("datastore")] @property def get_db_id(self): if self.db_id is None: self.db_id = self.appliance.host_id(self.name) return self.db_id else: return self.db_id def run_smartstate_analysis(self): """ Runs smartstate analysis on this host Note: The host must have valid credentials already set up for this to work. """ navigate_to(self, 'Details') tb.select('Configuration', 'Perform SmartState Analysis', invokes_alert=True) sel.handle_alert() flash.assert_message_contain( '"{}": Analysis successfully initiated'.format(self.name)) def check_compliance(self, timeout=240): """Initiates compliance check and waits for it to finish.""" navigate_to(self, 'Details') original_state = self.compliance_status tb.select('Policy', '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)) @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 """ sel.refresh() return self.get_detail('Compliance', 'Status') @property def is_compliant(self): """Check if the Host is compliant Returns: :py:class:`bool` """ text = self.compliance_status.strip().lower() if 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 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 must 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 navigate_to(self, 'Details') list_acc.select( 'Relationships', version.pick({ version.LOWEST: 'Show host drift history', '5.4': 'Show Host 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, 10)) 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_loc_map = { 'Properties': 'Properties', 'Security': 'Security', 'Configuration': 'Configuration', 'My Company Tags': 'Categories' } active_sec_loc = "//div[@id='all_sections_treebox']//li[contains(@id, 'group_{}')]"\ "/span[contains(@class, 'dynatree-selected')]".format(sec_loc_map[section]) sec_checkbox_loc = "//div[@id='all_sections_treebox']//li[contains(@id, 'group_{}')]"\ "//span[contains(@class, 'dynatree-checkbox')]".format(sec_loc_map[section]) sec_apply_btn = "//div[@id='accordion']/a[contains(normalize-space(text()), 'Apply')]" # If the section is not active yet, activate it if not sel.is_displayed(active_sec_loc): sel.click(sec_checkbox_loc) sel.click(sec_apply_btn) if not tb.is_active("All attributes"): tb.select("All attributes") d_grid = DriftGrid() if any( d_grid.cell_indicates_change(row_text, i) for i in range(0, len(indexes))): return False return True def tag(self, tag, **kwargs): """Tags the system by given tag""" navigate_to(self, 'Details') mixins.add_tag(tag, **kwargs) def untag(self, tag): """Removes the selected tag off the system""" navigate_to(self, 'Details') mixins.remove_tag(tag)
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 """ save_changes = deferred_verpick({ '5.7': form_buttons.angular_save, '5.8': form_buttons.simple_save }) # TODO: # Temporary defining elements with "//input" as Input() is not working.Seems to be # with html elements,looking into it. quota_form = Form( fields=[('cpu_cb', CFMECheckbox('cpu_allocated') ), ('cpu_txt', "//input[@id='id_cpu_allocated']" ), ('memory_cb', CFMECheckbox('mem_allocated') ), ('memory_txt', "//input[@id='id_mem_allocated']"), ('storage_cb', CFMECheckbox('storage_allocated') ), ('storage_txt', "//input[@id='id_storage_allocated']" ), ('vm_cb', CFMECheckbox('vms_allocated') ), ('vm_txt', "//input[@id='id_vms_allocated']"), ('template_cb', CFMECheckbox('templates_allocated') ), ('template_txt', "//input[@id='id_templates_allocated']")]) tenant_form = Form( fields=[('name', Input('name')), ('description', Input('description'))]) pretty_attrs = ["name", "description"] @classmethod 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] def create(self, cancel=False): if self._default: raise ValueError("Cannot create the root tenant {}".format( self.name)) navigate_to(self, 'Add') fill(self.tenant_form, self, action=form_buttons.add) if type(self) is Tenant: flash.assert_success_message('Tenant "{}" was saved'.format( self.name)) elif type(self) is Project: flash.assert_success_message('Project "{}" was saved'.format( self.name)) else: raise TypeError( 'No Tenant or Project class passed to create method{}'.format( type(self).__name__)) def update(self, updates): navigate_to(self, 'Edit') # Workaround - form is appearing after short delay sel.wait_for_element(self.tenant_form.description) fill(self.tenant_form, updates, action=self.save_changes) flash.assert_success_message('Project "{}" was saved'.format( updates.get('name', self.name))) def delete(self, cancel=False): navigate_to(self, 'Details') tb_select("Delete this item", invokes_alert=True) sel.handle_alert(cancel=cancel) flash.assert_success_message('Tenant "{}": Delete successful'.format( self.description)) def set_quota(self, **kwargs): navigate_to(self, 'ManageQuotas') # Workaround - form is appearing after short delay sel.wait_for_element(self.quota_form.cpu_txt) fill(self.quota_form, { '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') }, action=self.save_changes) flash.assert_success_message( 'Quotas for Tenant "{}" were saved'.format(self.name))
class OpenStackInstance(Instance): # CFME & provider power control options START = "Start" # START also covers RESUME and UNPAUSE (same as in CFME 5.4+ web UI) POWER_ON = START # For compatibility with the infra objects. SUSPEND = "Suspend" TERMINATE = "Terminate" # CFME-only power control options SOFT_REBOOT = "Soft Reboot" HARD_REBOOT = "Hard Reboot" # Provider-only power control options STOP = "Stop" PAUSE = "Pause" RESTART = "Restart" # CFME power states STATE_ON = "on" STATE_OFF = "off" STATE_ERROR = "non-operational" STATE_PAUSED = deferred_verpick({ version.LOWEST: "off", "5.4": "paused", }) STATE_SUSPENDED = deferred_verpick({ version.LOWEST: "off", "5.4": "suspended", }) STATE_UNKNOWN = "unknown" def create(self, email=None, first_name=None, last_name=None, cloud_network=None, instance_type=None, cancel=False, **prov_fill_kwargs): """Provisions an OpenStack instance with the given properties through CFME Args: email: Email of the requester first_name: Name of the requester last_name: Surname of the requester cloud_network: Name of the cloud network the instance should belong to instance_type: Type of the instance cancel: Clicks the cancel button if `True`, otherwise clicks the submit button (Defaults to `False`) Note: For more optional keyword arguments, see :py:data:`cfme.cloud.provisioning.provisioning_form` """ from cfme.provisioning import provisioning_form sel.force_navigate('clouds_provision_instances', context={ 'provider': self.provider, 'template_name': self.template_name, }) # not supporting multiselect now, just take first value security_groups = prov_fill_kwargs.pop('security_groups', None) if security_groups: prov_fill_kwargs['security_groups'] = security_groups[0] fill(provisioning_form, dict( email=email, first_name=first_name, last_name=last_name, instance_name=self.name, instance_type=instance_type, cloud_network=cloud_network, **prov_fill_kwargs )) if cancel: sel.click(provisioning_form.cancel_button) flash.assert_success_message( "Add of new VM Provision Request was cancelled by the user") else: sel.click(provisioning_form.submit_button) flash.assert_success_message( "VM Provision Request was Submitted, you will be notified when your VMs are ready") row_description = 'Provision from [%s] to [%s]' % (self.template_name, self.name) cells = {'Description': row_description} row, __ = wait_for(requests.wait_for_request, [cells], fail_func=requests.reload, num_sec=600, delay=20) assert row.last_message.text == version.pick( {version.LOWEST: 'VM Provisioned Successfully', "5.3": 'Vm Provisioned Successfully', }) def power_control_from_provider(self, option): """Power control the instance from the provider Args: option: power control action to take against instance Raises: OptionNotAvailable: option param must have proper value """ if option == OpenStackInstance.START: self.provider.mgmt.start_vm(self.name) elif option == OpenStackInstance.STOP: self.provider.mgmt.stop_vm(self.name) elif option == OpenStackInstance.SUSPEND: self.provider.mgmt.suspend_vm(self.name) elif option == OpenStackInstance.RESUME: self.provider.mgmt.resume_vm(self.name) elif option == OpenStackInstance.PAUSE: self.provider.mgmt.pause_vm(self.name) elif option == OpenStackInstance.UNPAUSE: self.provider.mgmt.unpause_vm(self.name) elif option == OpenStackInstance.RESTART: self.provider.mgmt.restart_vm(self.name) elif option == OpenStackInstance.TERMINATE: self.provider.mgmt.delete_vm(self.name) else: raise OptionNotAvailable(option + " is not a supported action")
class Host(Updateable, Pretty): """ Model of an infrastructure host in cfme. Args: name: Name of the host. hostname: Hostname of the host. ip_address: The IP address as a string. custom_ident: The custom identifiter. host_platform: Included but appears unused in CFME at the moment. ipmi_address: The IPMI address. mac_address: The mac address of the system. credentials (Credential): see Credential inner class. ipmi_credentials (Credential): see Credential inner class. Usage: myhost = Host(name='vmware', credentials=Provider.Credential(principal='admin', secret='foobar')) myhost.create() """ pretty_attrs = ['name', 'hostname', 'ip_address', 'custom_ident'] forced_saved = deferred_verpick( {version.LOWEST: form_buttons.FormButton( "Save Changes", dimmed_alt="Save", force_click=True), '5.5': form_buttons.FormButton( "Save changes", dimmed_alt="Save changes", force_click=True)}) def __init__(self, name=None, hostname=None, ip_address=None, custom_ident=None, host_platform=None, ipmi_address=None, mac_address=None, credentials=None, ipmi_credentials=None, interface_type='lan'): self.name = name self.hostname = hostname self.ip_address = ip_address self.custom_ident = custom_ident self.host_platform = host_platform self.ipmi_address = ipmi_address self.mac_address = mac_address self.credentials = credentials self.ipmi_credentials = ipmi_credentials self.interface_type = interface_type self.db_id = None def _form_mapping(self, create=None, **kwargs): return {'name_text': kwargs.get('name'), 'hostname_text': kwargs.get('hostname'), 'ipaddress_text': kwargs.get('ip_address'), 'custom_ident_text': kwargs.get('custom_ident'), 'host_platform': kwargs.get('host_platform'), 'ipmi_address_text': kwargs.get('ipmi_address'), 'mac_address_text': kwargs.get('mac_address')} class Credential(cfme.Credential, Updateable): """Provider credentials Args: **kwargs: If using IPMI type credential, ipmi = True""" def __init__(self, **kwargs): super(Host.Credential, self).__init__(**kwargs) self.ipmi = kwargs.get('ipmi') def _submit(self, cancel, submit_button): if cancel: sel.click(form_buttons.cancel) # sel.wait_for_element(page.configuration_btn) else: sel.click(submit_button) flash.assert_no_errors() def create(self, cancel=False, validate_credentials=False): """ Creates a host in the UI Args: cancel (boolean): Whether to cancel out of the creation. The cancel is done after all the information present in the Host has been filled in the UI. validate_credentials (boolean): Whether to validate credentials - if True and the credentials are invalid, an error will be raised. """ sel.force_navigate('infrastructure_host_new') fill(properties_form, self._form_mapping(True, **self.__dict__)) fill(credential_form, self.credentials, validate=validate_credentials) fill(credential_form, self.ipmi_credentials, validate=validate_credentials) self._submit(cancel, host_add_btn) def update(self, updates, cancel=False, validate_credentials=False): """ Updates a host in the UI. Better to use utils.update.update context manager than call this directly. Args: updates (dict): fields that are changing. cancel (boolean): whether to cancel out of the update. """ sel.force_navigate('infrastructure_host_edit', context={'host': self}) change_stored_password() fill(credential_form, updates.get('credentials', None), validate=validate_credentials) # Workaround for issue with form_button staying dimmed. try: logger.debug("Trying to save update for host with id: " + str(self.get_db_id)) self._submit(cancel, self.forced_saved) logger.debug("save worked, no exception") except Exception as e: logger.debug("exception detected: " + str(e)) sel.browser().execute_script( "$j.ajax({type: 'POST', url: '/host/form_field_changed/%s'," " data: {'default_userid':'%s'}})" % (str(sel.current_url().split('/')[5]), updates.get('credentials', None).principal)) sel.browser().execute_script( "$j.ajax({type: 'POST', url: '/host/form_field_changed/%s'," " data: {'default_password':'******'}})" % (str(sel.current_url().split('/')[5]), updates.get('credentials', None).secret)) sel.browser().execute_script( "$j.ajax({type: 'POST', url: '/host/form_field_changed/%s'," " data: {'default_verify':'%s'}})" % (str(sel.current_url().split('/')[5]), updates.get('credentials', None).verify_secret)) self._submit(cancel, self.forced_saved) def delete(self, cancel=True): """ Deletes a host from CFME Args: cancel: Whether to cancel the deletion, defaults to True """ sel.force_navigate('infrastructure_host', context={'host': self}) cfg_btn('Remove from the VMDB', invokes_alert=True) sel.handle_alert(cancel=cancel) def execute_button(self, button_group, button, cancel=True): sel.force_navigate('infrastructure_host', context={'host': self}) host_btn = partial(tb.select, button_group) host_btn(button, invokes_alert=True) sel.click(form_buttons.submit) flash.assert_success_message("Order Request was Submitted") host_btn(button, invokes_alert=True) sel.click(form_buttons.cancel) flash.assert_success_message("Service Order was cancelled by the user") def power_on(self): sel.force_navigate('infrastructure_host', context={'host': self}) pow_btn('Power On') sel.handle_alert() def power_off(self): sel.force_navigate('infrastructure_host', context={'host': self}) pow_btn('Power Off') sel.handle_alert() def get_ipmi(self): return IPMI(hostname=self.ipmi_address, username=self.ipmi_credentials.principal, password=self.ipmi_credentials.secret, interface_type=self.interface_type) def get_detail(self, *ident): """ Gets details from the details infoblock The function first ensures that we are on the detail page for the specific host. Args: *ident: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Returns: A string representing the contents of the InfoBlock's value. """ if not self._on_detail_page(): sel.force_navigate('infrastructure_host', context={'host': self}) return details_page.infoblock.text(*ident) def _on_detail_page(self): """ Returns ``True`` if on the hosts detail page, ``False`` if not.""" return sel.is_displayed('//div[@class="dhtmlxInfoBarLabel-2"][contains(., "%s")]' % self.name) @property def exists(self): sel.force_navigate('infrastructure_hosts') for page in paginator.pages(): if sel.is_displayed(Quadicon(self.name, 'host')): return True else: return False @property def has_valid_credentials(self): """ Check if host has valid credentials saved Returns: ``True`` if credentials are saved and valid; ``False`` otherwise """ sel.force_navigate('infrastructure_hosts') quad = Quadicon(self.name, 'host') return quad.creds == 'checkmark' def _assign_unassign_policy_profiles(self, assign, *policy_profile_names): """DRY function for managing policy profiles. See :py:func:`assign_policy_profiles` and :py:func:`assign_policy_profiles` Args: assign: Wheter to assign or unassign. policy_profile_names: :py:class:`str` with Policy Profile names. """ sel.force_navigate('infrastructure_host_policy_assignment', context={'host': self}) for policy_profile in policy_profile_names: if assign: manage_policies_tree.check_node(policy_profile) else: manage_policies_tree.uncheck_node(policy_profile) sel.click(form_buttons.save) def assign_policy_profiles(self, *policy_profile_names): """ Assign Policy Profiles to this Host. Args: policy_profile_names: :py:class:`str` with Policy Profile names. After Control/Explorer coverage goes in, PolicyProfile objects will be also passable. """ self._assign_unassign_policy_profiles(True, *policy_profile_names) def unassign_policy_profiles(self, *policy_profile_names): """ Unssign Policy Profiles to this Host. Args: policy_profile_names: :py:class:`str` with Policy Profile names. After Control/Explorer coverage goes in, PolicyProfile objects will be also passable. """ self._assign_unassign_policy_profiles(False, *policy_profile_names) def get_datastores(self): """ Gets list of all datastores used by this host""" sel.force_navigate('infrastructure_host', context={'host': self}) list_acc.select('Relationships', version.pick({version.LOWEST: 'Show Datastores', '5.3': 'Show all Datastores'})) datastores = set([]) for page in paginator.pages(): for title in sel.elements( "//div[@id='quadicon']/../../../tr/td/a[contains(@href,'storage/show')]"): datastores.add(sel.get_attribute(title, "title")) return datastores @property def get_db_id(self): if self.db_id is None: self.db_id = get_host_id(self.name) return self.db_id else: return self.db_id def run_smartstate_analysis(self): """ Runs smartstate analysis on this host Note: The host must have valid credentials already set up for this to work. """ sel.force_navigate('infrastructure_host', context={'host': self}) tb.select('Configuration', 'Perform SmartState Analysis', invokes_alert=True) sel.handle_alert() flash.assert_message_contain('"{}": Analysis successfully initiated'.format(self.name)) 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 must 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 sel.force_navigate('infrastructure_host', context={'host': self}) list_acc.select('Relationships', version.pick({ version.LOWEST: 'Show host drift history', '5.4': 'Show Host 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, 10)) 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_loc_map = { 'Properties': 'Properties', 'Security': 'Security', 'Configuration': 'Configuration', 'My Company Tags': 'Categories'} active_sec_loc = "//div[@id='all_sections_treebox']//li[contains(@id, 'group_{}')]"\ "/span[contains(@class, 'dynatree-selected')]".format(sec_loc_map[section]) sec_checkbox_loc = "//div[@id='all_sections_treebox']//li[contains(@id, 'group_{}')]"\ "//span[contains(@class, 'dynatree-checkbox')]".format(sec_loc_map[section]) sec_apply_btn = "//div[@id='accordion']/a[contains(normalize-space(text()), 'Apply')]" # If the section is not active yet, activate it if not sel.is_displayed(active_sec_loc): sel.click(sec_checkbox_loc) sel.click(sec_apply_btn) if not tb.is_active("All attributes"): tb.select("All attributes") d_grid = DriftGrid() if any(d_grid.cell_indicates_change(row_text, i) for i in range(0, len(indexes))): return False return True def tag(self, tag, **kwargs): """Tags the system by given tag""" sel.force_navigate('infrastructure_host', context={'host': self}) mixins.add_tag(tag, **kwargs) def untag(self, tag): """Removes the selected tag off the system""" sel.force_navigate('infrastructure_host', context={'host': self}) mixins.remove_tag(tag)
class OpenStackInstance(Instance): # CFME & provider power control options START = "Start" # START also covers RESUME and UNPAUSE (same as in CFME 5.4+ web UI) POWER_ON = START # For compatibility with the infra objects. SUSPEND = "Suspend" DELETE = "Delete" TERMINATE = deferred_verpick({ version.LOWEST: 'Terminate', '5.6.1': 'Delete', }) # CFME-only power control options SOFT_REBOOT = "Soft Reboot" HARD_REBOOT = "Hard Reboot" # Provider-only power control options STOP = "Stop" PAUSE = "Pause" RESTART = "Restart" SHELVE = "Shelve" SHELVE_OFFLOAD = "Shelve Offload" # CFME power states STATE_ON = "on" STATE_OFF = "off" STATE_ERROR = "non-operational" STATE_PAUSED = "paused" STATE_SUSPENDED = "suspended" STATE_REBOOTING = "reboot_in_progress" STATE_SHELVED = "shelved" STATE_SHELVED_OFFLOAD = "shelved_offloaded" STATE_UNKNOWN = "unknown" STATE_ARCHIVED = "archived" STATE_TERMINATED = "terminated" @property def ui_powerstates_available(self): return { 'on': [self.SUSPEND, self.SOFT_REBOOT, self.HARD_REBOOT, self.TERMINATE], 'off': [self.START, self.TERMINATE] } @property def ui_powerstates_unavailable(self): return { 'on': [self.START], 'off': [self.SUSPEND, self.SOFT_REBOOT, self.HARD_REBOOT] } def create(self, cancel=False, **prov_fill_kwargs): """Provisions an OpenStack instance with the given properties through CFME Args: cancel: Clicks the cancel button if `True`, otherwise clicks the submit button (Defaults to `False`) prov_fill_kwargs: dictionary of provisioning field/value pairs Note: For more optional keyword arguments, see :py:data:`cfme.cloud.provisioning.ProvisioningForm` """ super(OpenStackInstance, self).create(form_values=prov_fill_kwargs, cancel=cancel) def power_control_from_provider(self, option): """Power control the instance from the provider Args: option: power control action to take against instance Raises: OptionNotAvailable: option param must have proper value """ if option == OpenStackInstance.START: self.provider.mgmt.start_vm(self.name) elif option == OpenStackInstance.STOP: self.provider.mgmt.stop_vm(self.name) elif option == OpenStackInstance.SUSPEND: self.provider.mgmt.suspend_vm(self.name) elif option == OpenStackInstance.PAUSE: self.provider.mgmt.pause_vm(self.name) elif option == OpenStackInstance.SHELVE: # TODO: rewrite it once wrapanapi will get shelve # and shelve_offload methods self.provider.mgmt._find_instance_by_name(self.name).shelve() elif option == OpenStackInstance.SHELVE_OFFLOAD: self.provider.mgmt._find_instance_by_name( self.name).shelve_offload() elif option == OpenStackInstance.RESTART: self.provider.mgmt.restart_vm(self.name) elif option == OpenStackInstance.TERMINATE: self.provider.mgmt.delete_vm(self.name) else: raise OptionNotAvailable(option + " is not a supported action")
class Timeprofile(Updateable, Navigatable): timeprofile_form = Form( fields=[ ("description", Input("description")), ("scope", AngularSelect("profile_type")), ("timezone", AngularSelect("profile_tz")), ("days", CFMECheckbox("all_days")), ("hours", CFMECheckbox("all_hours")), ] ) save_edit_button = deferred_verpick({'5.7': form_buttons.FormButton('Save changes'), '5.8': form_buttons.FormButton('Save')}) save_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Add this Time Profile"), '5.7': form_buttons.FormButton('Add'), '5.8': form_buttons.FormButton('Save') }) def __init__(self, description=None, scope=None, days=None, hours=None, timezone=None, appliance=None): Navigatable.__init__(self, appliance=appliance) self.description = description self.scope = scope self.days = days self.hours = hours self.timezone = timezone def create(self, cancel=False): navigate_to(self, 'Add') fill(self.timeprofile_form, {'description': self.description, 'scope': self.scope, 'days': self.days, 'hours': self.hours, 'timezone': self.timezone, }) if not cancel: sel.click(self.save_button) end = "saved" if self.appliance.version > '5.7' else "added" flash.assert_success_message('Time Profile "{}" was {}' .format(self.description, end)) def update(self, updates): navigate_to(self, 'Edit') fill(self.timeprofile_form, {'description': updates.get('description'), 'scope': updates.get('scope'), 'timezone': updates.get('timezone')}, action={version.LOWEST: form_buttons.save, '5.7': self.save_edit_button}) flash.assert_success_message( 'Time Profile "{}" was saved'.format(updates.get('description', self.description))) def copy(self): navigate_to(self, 'All') row = timeprofile_table.find_row_by_cells({'description': self.description}) sel.check(sel.element(".//input[@type='checkbox']", root=row[0])) cfg_btn('Copy selected Time Profile') new_timeprofile = Timeprofile(description=self.description + "copy", scope=self.scope) fill(self.timeprofile_form, {'description': new_timeprofile.description, 'scope': new_timeprofile.scope}, action=self.save_button) end = "saved" if self.appliance.version > '5.7' else "added" flash.assert_success_message('Time Profile "{}" was {}' .format(new_timeprofile.description, end)) return new_timeprofile def delete(self): navigate_to(self, 'All') row = timeprofile_table.find_row_by_cells({'description': self.description}) sel.check(sel.element(".//input[@type='checkbox']", root=row[0])) cfg_btn('Delete selected Time Profiles', invokes_alert=True) sel.handle_alert() flash.assert_success_message( 'Time Profile "{}": Delete successful'.format(self.description))
class Provider(Pretty, CloudInfraProvider): """ Abstract model of an infrastructure provider in cfme. See VMwareProvider or RHEVMProvider. Args: name: Name of the provider. details: a details record (see VMwareDetails, RHEVMDetails inner class). credentials (Credential): see Credential inner class. key: The CFME key of the provider in the yaml. candu: C&U credentials if this is a RHEVMDetails class. Usage: myprov = VMwareProvider(name='foo', region='us-west-1', credentials=Provider.Credential(principal='admin', secret='foobar')) myprov.create() """ type_tclass = "infra" pretty_attrs = ['name', 'key', 'zone'] STATS_TO_MATCH = [ 'num_template', 'num_vm', 'num_datastore', 'num_host', 'num_cluster' ] string_name = "Infrastructure" page_name = "infrastructure" instances_page_name = "infra_vm_and_templates" templates_page_name = "infra_vm_and_templates" quad_name = "infra_prov" _properties_region = prop_region # This will get resolved in common to a real form add_provider_button = deferred_verpick({ version.LOWEST: form_buttons.FormButton("Add this Infrastructure Provider"), '5.6': form_buttons.add }) save_button = deferred_verpick({ version.LOWEST: form_buttons.save, '5.6': form_buttons.angular_save }) def __init__(self, name=None, credentials=None, key=None, zone=None, provider_data=None): if not credentials: credentials = {} self.name = name self.credentials = credentials self.key = key self.provider_data = provider_data self.zone = zone self.vm_name = version.pick({ version.LOWEST: "VMs", '5.5': "VMs and Instances" }) self.template_name = "Templates" def _form_mapping(self, create=None, **kwargs): return {'name_text': kwargs.get('name')} @variable(alias='db') def num_datastore(self): storage_table_name = version.pick({ version.LOWEST: 'hosts_storages', '5.5.0.8': 'host_storages' }) """ Returns the providers number of templates, as shown on the Details page.""" results = list(cfmedb().engine.execute( 'SELECT DISTINCT storages.name, hosts.ems_id ' 'FROM ext_management_systems, hosts, storages, {} ' 'WHERE hosts.id={}.host_id AND ' 'storages.id={}.storage_id AND ' 'hosts.ems_id=ext_management_systems.id AND ' 'ext_management_systems.name=\'{}\''.format( storage_table_name, storage_table_name, storage_table_name, self.name))) return len(results) @num_datastore.variant('ui') def num_datastore_ui(self): return int(self.get_detail("Relationships", "Datastores")) @variable(alias='rest') def num_host(self): provider = rest_api().collections.providers.find_by(name=self.name)[0] num_host = 0 for host in rest_api().collections.hosts: if host['ems_id'] == provider.id: num_host += 1 return num_host @num_host.variant('db') def num_host_db(self): ext_management_systems = cfmedb()["ext_management_systems"] hosts = cfmedb()["hosts"] hostlist = list(cfmedb().session.query(hosts.name).join( ext_management_systems, hosts.ems_id == ext_management_systems.id).filter( ext_management_systems.name == self.name)) return len(hostlist) @num_host.variant('ui') def num_host_ui(self): if version.current_version() < "5.6": host_src = "host.png" node_src = "node.png" else: host_src = "host-" node_src = "node-" try: num = int(self.get_detail("Relationships", host_src, use_icon=True)) except sel.NoSuchElementException: logger.error( "Couldn't find number of hosts using key [Hosts] trying Nodes") num = int(self.get_detail("Relationships", node_src, use_icon=True)) return num @variable(alias='rest') def num_cluster(self): provider = rest_api().collections.providers.find_by(name=self.name)[0] num_cluster = 0 for cluster in rest_api().collections.clusters: if cluster['ems_id'] == provider.id: num_cluster += 1 return num_cluster @num_cluster.variant('db') def num_cluster_db(self): """ Returns the providers number of templates, as shown on the Details page.""" ext_management_systems = cfmedb()["ext_management_systems"] clusters = cfmedb()["ems_clusters"] clulist = list(cfmedb().session.query(clusters.name).join( ext_management_systems, clusters.ems_id == ext_management_systems.id).filter( ext_management_systems.name == self.name)) return len(clulist) @num_cluster.variant('ui') def num_cluster_ui(self): if version.current_version() < "5.6": return int( self.get_detail("Relationships", "cluster.png", use_icon=True)) else: return int( self.get_detail("Relationships", "cluster-", use_icon=True)) def discover(self): """ Begins provider discovery from a provider instance Usage: discover_from_config(utils.providers.get_crud('rhevm')) """ from virtualcenter import VMwareProvider from rhevm import RHEVMProvider from scvmm import SCVMMProvider vmware = isinstance(self, VMwareProvider) rhevm = isinstance(self, RHEVMProvider) scvmm = isinstance(self, SCVMMProvider) discover(rhevm, vmware, scvmm, cancel=False, start_ip=self.start_ip, end_ip=self.end_ip) @property def hosts(self): """Returns list of :py:class:`cfme.infrastructure.host.Host` that should belong to this provider according to the YAML """ result = [] for host in self.get_yaml_data().get("hosts", []): creds = conf.credentials.get(host["credentials"], {}) cred = Host.Credential( principal=creds["username"], secret=creds["password"], verify_secret=creds["password"], ) result.append(Host(name=host["name"], credentials=cred)) return result
class GCEInstance(Instance): # CFME & provider power control options START = "Start" POWER_ON = START # For compatibility with the infra objects. STOP = "Stop" DELETE = "Delete" TERMINATE = deferred_verpick({ version.LOWEST: 'Terminate', '5.6.1': 'Delete', }) # CFME-only power control options SOFT_REBOOT = "Soft Reboot" # Provider-only power control options RESTART = "Restart" # CFME power states STATE_ON = "on" STATE_OFF = "off" STATE_SUSPENDED = "suspended" STATE_TERMINATED = "terminated" STATE_ARCHIVED = "archived" STATE_UNKNOWN = "unknown" @property def ui_powerstates_available(self): return { 'on': [self.STOP, self.SOFT_REBOOT, self.TERMINATE], 'off': [self.START, self.TERMINATE] } @property def ui_powerstates_unavailable(self): return {'on': [self.START], 'off': [self.STOP, self.SOFT_REBOOT]} def create(self, email=None, first_name=None, last_name=None, availability_zone=None, instance_type=None, cloud_network=None, boot_disk_size=None, cancel=False, **prov_fill_kwargs): """Provisions an GCE instance with the given properties through CFME Args: email: Email of the requester first_name: Name of the requester last_name: Surname of the requester availability_zone: zone to deploy instance cloud_network: Name of the cloud network the instance should belong to instance_type: Type of the instance boot_disk_size: size of root disk cancel: Clicks the cancel button if `True`, otherwise clicks the submit button (Defaults to `False`) Note: For more optional keyword arguments, see :py:data:`cfme.cloud.provisioning.provisioning_form` """ from cfme.provisioning import provisioning_form # Nav to provision form and select image select_provision_image(template_name=self.template_name, provider=self.provider) fill( provisioning_form, dict(email=email, first_name=first_name, last_name=last_name, instance_name=self.name, availability_zone=availability_zone, instance_type=instance_type, cloud_network=cloud_network, boot_disk_size=boot_disk_size, **prov_fill_kwargs)) if cancel: sel.click(provisioning_form.cancel_button) flash.assert_success_message( "Add of new VM Provision Request was cancelled by the user") else: sel.click(provisioning_form.submit_button) flash.assert_success_message( "VM Provision Request was Submitted, you will be notified when your VMs are ready" ) def power_control_from_provider(self, option): """Power control the instance from the provider Args: option: power control action to take against instance Raises: OptionNotAvailable: option param must have proper value """ if option == GCEInstance.START: self.provider.mgmt.start_vm(self.name) elif option == GCEInstance.STOP: self.provider.mgmt.stop_vm(self.name) elif option == GCEInstance.RESTART: self.provider.mgmt.restart_vm(self.name) elif option == GCEInstance.TERMINATE: self.provider.mgmt.delete_vm(self.name) else: raise OptionNotAvailable(option + " is not a supported action")
class OpenStackInstance(Instance): # CFME & provider power control options START = "Start" # START also covers RESUME and UNPAUSE (same as in CFME 5.4+ web UI) POWER_ON = START # For compatibility with the infra objects. SUSPEND = "Suspend" DELETE = "Delete" TERMINATE = deferred_verpick({ version.LOWEST: 'Terminate', '5.6.1': 'Delete', }) # CFME-only power control options SOFT_REBOOT = "Soft Reboot" HARD_REBOOT = "Hard Reboot" # Provider-only power control options STOP = "Stop" PAUSE = "Pause" RESTART = "Restart" SHELVE = "Shelve" SHELVE_OFFLOAD = "Shelve Offload" # CFME power states STATE_ON = "on" STATE_OFF = "off" STATE_ERROR = "non-operational" STATE_PAUSED = "paused" STATE_SUSPENDED = "suspended" STATE_REBOOTING = "reboot_in_progress" STATE_SHELVED = "shelved" STATE_SHELVED_OFFLOAD = "shelved_offloaded" STATE_UNKNOWN = "unknown" STATE_ARCHIVED = "archived" STATE_TERMINATED = "terminated" @property def ui_powerstates_available(self): return { 'on': [self.SUSPEND, self.SOFT_REBOOT, self.HARD_REBOOT, self.TERMINATE], 'off': [self.START, self.TERMINATE]} @property def ui_powerstates_unavailable(self): return { 'on': [self.START], 'off': [self.SUSPEND, self.SOFT_REBOOT, self.HARD_REBOOT]} def create(self, email=None, first_name=None, last_name=None, cloud_network=None, instance_type=None, cancel=False, **prov_fill_kwargs): """Provisions an OpenStack instance with the given properties through CFME Args: email: Email of the requester first_name: Name of the requester last_name: Surname of the requester cloud_network: Name of the cloud network the instance should belong to instance_type: Type of the instance cancel: Clicks the cancel button if `True`, otherwise clicks the submit button (Defaults to `False`) Note: For more optional keyword arguments, see :py:data:`cfme.cloud.provisioning.provisioning_form` """ from cfme.provisioning import provisioning_form # Nav to provision form and select image select_provision_image(template_name=self.template_name, provider=self.provider) # not supporting multiselect now, just take first value security_groups = prov_fill_kwargs.pop('security_groups', None) if security_groups: prov_fill_kwargs['security_groups'] = security_groups[0] fill(provisioning_form, dict( email=email, first_name=first_name, last_name=last_name, instance_name=self.name, instance_type=instance_type, cloud_network=cloud_network, **prov_fill_kwargs )) if cancel: sel.click(provisioning_form.cancel_button) flash.assert_success_message( "Add of new VM Provision Request was cancelled by the user") else: sel.click(provisioning_form.submit_button) flash.assert_success_message( "VM Provision Request was Submitted, you will be notified when your VMs are ready") def power_control_from_provider(self, option): """Power control the instance from the provider Args: option: power control action to take against instance Raises: OptionNotAvailable: option param must have proper value """ if option == OpenStackInstance.START: self.provider.mgmt.start_vm(self.name) elif option == OpenStackInstance.STOP: self.provider.mgmt.stop_vm(self.name) elif option == OpenStackInstance.SUSPEND: self.provider.mgmt.suspend_vm(self.name) elif option == OpenStackInstance.PAUSE: self.provider.mgmt.pause_vm(self.name) elif option == OpenStackInstance.SHELVE: # TODO: rewrite it once mgmtsystem will get shelve # and shelve_offload methods self.provider.mgmt._find_instance_by_name(self.name).shelve() elif option == OpenStackInstance.SHELVE_OFFLOAD: self.provider.mgmt._find_instance_by_name(self.name).shelve_offload() elif option == OpenStackInstance.RESTART: self.provider.mgmt.restart_vm(self.name) elif option == OpenStackInstance.TERMINATE: self.provider.mgmt.delete_vm(self.name) else: raise OptionNotAvailable(option + " is not a supported action")
class Snapshot(object): snapshot_tree = deferred_verpick({ version.LOWEST: Tree("//div[@id='snapshots_treebox']/ul"), '5.7.0.1': BootstrapTreeview('snapshot_treebox') }) def __init__(self, name=None, description=None, memory=None, parent_vm=None): super(Vm.Snapshot, self).__init__() self.name = name self.description = description self.memory = memory self.vm = parent_vm def _nav_to_snapshot_mgmt(self): snapshot_title = '"Snapshots" for Virtual Machine "{}"'.format( self.vm.name) if summary_title() != snapshot_title: self.vm.load_details() sel.click(InfoBlock.element("Properties", "Snapshots")) @property def exists(self): self._nav_to_snapshot_mgmt() try: self.snapshot_tree.find_path_to( re.compile(r"{}.*?".format(self.name or self.description))) return True except CandidateNotFound: return False except NoSuchElementException: return False def _click_tree_path(self, prop): """Find and click the given property in a snapshot tree path. Args: prop (str): Property to check (name or description). Returns: None """ self.snapshot_tree.click_path( *self.snapshot_tree.find_path_to(re.compile(prop))) @property def active(self): """Check if the snapshot is active. Returns: bool: True if snapshot is active, False otherwise. """ self._nav_to_snapshot_mgmt() try: self._click_tree_path(self.name or self.description) if sel.is_displayed_text("{} (Active)".format( self.name or self.description)): return True except CandidateNotFound: return False def create(self): snapshot_dict = {'description': self.description} self._nav_to_snapshot_mgmt() toolbar.select('Create a new snapshot for this VM') if self.name is not None: snapshot_dict['name'] = self.name if self.vm.provider.mgmt.is_vm_running(self.vm.name): snapshot_dict["snapshot_memory"] = self.memory fill(snapshot_form, snapshot_dict, action=snapshot_form.create_button) wait_for(lambda: self.exists, num_sec=300, delay=20, fail_func=sel.refresh, handle_exception=True) def delete(self, cancel=False): self._nav_to_snapshot_mgmt() toolbar.select('Delete Snapshots', 'Delete Selected Snapshot', invokes_alert=True) sel.handle_alert(cancel=cancel) if not cancel: flash.assert_message_match( 'Remove Snapshot initiated for 1 ' 'VM and Instance from the CFME Database') def delete_all(self, cancel=False): self._nav_to_snapshot_mgmt() toolbar.select('Delete Snapshots', 'Delete All Existing Snapshots', invokes_alert=True) sel.handle_alert(cancel=cancel) if not cancel: flash.assert_message_match( 'Remove All Snapshots initiated for 1 VM and ' 'Instance from the CFME Database') def revert_to(self, cancel=False): self._nav_to_snapshot_mgmt() self._click_tree_path(self.name or self.description) toolbar.select('Revert to selected snapshot', invokes_alert=True) sel.handle_alert(cancel=cancel) flash.assert_message_match( 'Revert To Snapshot initiated for 1 VM and Instance from ' 'the CFME Database')
class ButtonGroup(Updateable): """Create,Edit and Delete Button Groups Args: text: The button Group name. hover: The button group hover text. type: The object type. """ CLUSTER = "Cluster" DATASTORE = "Datastore" HOST = deferred_verpick({version.LOWEST: "Host", '5.4': "Host / Node"}) PROVIDER = "Provider" SERVICE = "Service" TEMPLATE = "VM Template and Image" VM_INSTANCE = "VM and Instance" def __init__(self, text=None, hover=None, type=None): self.text = text self.hover = hover self.type = type 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 update(self, updates): sel.force_navigate('group_button_edit', context={"buttongroup": self}) edited_hvr_text = updates.get('hover', None) fill(button_group_form, {'btn_group_hvr_text': edited_hvr_text}) sel.click(button_group_form.save_button) flash.assert_success_message( 'Buttons Group "{}" was saved'.format(edited_hvr_text)) def delete(self): sel.force_navigate('button_group', context={"buttongroup": self}) cfg_btn("Remove this Button Group", invokes_alert=True) sel.handle_alert() flash.assert_success_message( 'Buttons Group "{}": Delete successful'.format(self.hover)) @property def exists(self): try: sel.force_navigate('button_group', context={"buttongroup": self}) return True except CandidateNotFound: return False def delete_if_exists(self): if self.exists: self.delete()
class Widget(Pretty): _name = deferred_verpick({ version.LOWEST: "//div[@id='{}']//span[contains(@class, 'modtitle_text')]", "5.5": "//div[@id='{}']//h3", "5.6": "//div[@id='{}']//h2[contains(@class, 'card-pf-title')]" }) _remove = "//div[@id='{}']//a[@title='Remove from Dashboard']" _minimize = "//div[@id='{}']//a[@title='Minimize']" _restore = "//div[@id='{}']//a[@title='Restore']" _footer = deferred_verpick({ version.LOWEST: "//div[@id='{}']//div[@class='modboxfooter' or contains(@class, 'panel-footer')]", "5.6": "//div[@id='{}']//div[contains(@class, 'card-pf-footer')]" }) _zoom = "//div[@id='{}']//a[@title='Zoom in on this chart']" _zoomed_name = deferred_verpick({ version.LOWEST: "//div[@id='lightbox_div']//span[contains(@class, 'modtitle_text')]", "5.5": "//div[@id='lightbox_div']//h3" }) _zoomed_close = deferred_verpick({ version.LOWEST: "//div[@id='lightbox_div']//a[@title='Close']", "5.5": "//div[@id='lightbox_div']//a[@title='Close']/i" }) _all = "//div[@id='modules']//div[contains(@id, 'w_')]" _content = deferred_verpick({ version.LOWEST: "//div[@id='{}']//div[contains(@class, 'modboxin')]", "5.5": "//div[@id='{}']//div[contains(@class,'panel-body')]/div", "5.6": "//div[@id='{}']//div[contains(@class,'card-pf-body')]/div" }) _content_type = deferred_verpick({ version.LOWEST: "//div[@id='{}']//div[contains(@class, 'modboxin')]/../h2/a[1]", "5.5": "//div[@id='{}']//div[contains(@class,'panel-body')]", "5.6": "//div[@id='{}']//div[contains(@class,'card-pf-body')]" }) # 5.5+ updated _menu_opener = deferred_verpick({ version.LOWEST: "//div[@id='{}']//a[contains(@class, 'dropdown-toggle')]/i", "5.6": "//div[@id='{}']//button[contains(@class, 'dropdown-toggle')]" }) _menu_container = "//div[@id='{}']//ul[contains(@class, 'dropdown-menu')]" _menu_minmax = _menu_container + "/li/a[contains(@id, 'minmax')]" _menu_remove = _menu_container + "/li/a[contains(@id, 'close')]" _menu_zoom = _menu_container + "/li/a[contains(@id, 'zoom')]" pretty_attrs = ['_div_id'] def __init__(self, div_id): self._div_id = div_id @property def newer_version(self): return version.current_version() >= "5.5" @property def name(self): return sel.text(self._name.format(self._div_id)).encode("utf-8") @property def content_type(self): if version.current_version() <= "5.4": return sel.get_attribute(self._content_type.format(self._div_id), "class").strip() else: return sel.get_attribute(self._content_type.format(self._div_id), "class").rsplit(" ", 1)[-1] @property def content(self): if self.content_type in {"rss_widget", "rssbox"}: return RSSWidgetContent(self._div_id) elif self.content_type in {"report_widget", "reportbox"}: return ReportWidgetContent(self._div_id) else: return BaseWidgetContent(self._div_id) @property def footer(self): cleaned = [ x.strip() for x in sel.text(self._footer.format( self._div_id)).encode("utf-8").strip().split("|") ] result = {} for item in cleaned: name, time = item.split(" ", 1) time = time.strip() if time.lower() == "never": result[name.strip().lower()] = None else: result[name.strip().lower()] = parsetime.from_american_minutes( time.strip()) return result @property def time_updated(self): return self.footer["updated"] @property def time_next(self): return self.footer["next"] @property def is_minimized(self): self.close_zoom() if not self.newer_version: return not sel.is_displayed(self._minimize.format(self._div_id)) else: return not sel.is_displayed(self._content.format(self._div_id)) @property def can_zoom(self): """Can this Widget be zoomed?""" self.close_zoom() if not self.newer_version: return sel.is_displayed(self._zoom.format(self._div_id)) else: self.open_dropdown_menu() zoomable = sel.is_displayed(self._menu_zoom.format(self._div_id)) self.close_dropdown_menu() return zoomable def _click_menu_button_by_loc(self, loc): self.close_zoom() try: self.open_dropdown_menu() sel.click(loc.format(self._div_id)) finally: self.close_dropdown_menu() def remove(self, cancel=False): """Remove this Widget.""" self.close_zoom() if not self.newer_version: sel.click(self._remove.format(self._div_id), wait_ajax=False) # alert sel.handle_alert(cancel) else: self._click_menu_button_by_loc(self._menu_remove) def minimize(self): """Minimize this Widget.""" self.close_zoom() if not self.is_minimized: if not self.newer_version: sel.click(self._minimize.format(self._div_id)) else: self._click_menu_button_by_loc(self._menu_minmax) def restore(self): """Return the Widget back from minimalization.""" self.close_zoom() if self.is_minimized: if not self.newer_version: sel.click(self._restore.format(self._div_id)) else: self._click_menu_button_by_loc(self._menu_minmax) def zoom(self): """Zoom this Widget.""" self.close_zoom() if not self.is_zoomed(): if not self.newer_version: sel.click(self._zoom.format(self._div_id)) else: self._click_menu_button_by_loc(self._menu_zoom) @classmethod def is_zoomed(cls): return sel.is_displayed(cls._zoomed_name) @classmethod def get_zoomed_name(cls): return sel.text(cls._zoomed_name).encode("utf-8").strip() @classmethod def close_zoom(cls): if cls.is_zoomed(): sel.click(cls._zoomed_close) # Here no ajax, so we have to check it manually wait_for(lambda: not cls.is_zoomed(), delay=0.1, num_sec=5, message="cancel zoom") @classmethod def all(cls): """Returns objects with all Widgets currently present.""" sel.force_navigate('dashboard') result = [] for el in sel.elements(cls._all): result.append(cls(sel.get_attribute(el, "id"))) return result @classmethod def by_name(cls, name): """Returns Widget with specified name.""" for widget in cls.all(): if widget.name == name: return widget else: raise NameError( "Could not find widget with name {} on current dashboard!". format(name)) @classmethod def by_type(cls, content_type): """Returns Widget with specified content_type.""" return filter(lambda w: w.content_type == content_type, cls.all()) # 5.5+ specific methods @property def is_dropdown_menu_opened(self): return sel.is_displayed(self._menu_container.format(self._div_id)) @property def drag_element(self): return sel.element(self._name.format(self._div_id)) @property def drop_element(self): return sel.element(self._footer.format(self._div_id)) def open_dropdown_menu(self): if not sel.is_displayed(self._menu_opener.format(self._div_id)): return # Not a 5.5+ self.close_dropdown_menu() sel.click(self._menu_opener.format(self._div_id)) wait_for(lambda: self.is_dropdown_menu_opened, num_sec=10, delay=0.2, message="widget dropdown menu opend") def close_dropdown_menu(self): if not sel.is_displayed(self._menu_opener.format(self._div_id)): return # Not a 5.5+ if self.is_dropdown_menu_opened: sel.click("//a[contains(@class, 'navbar-brand')]/img") wait_for(lambda: not self.is_dropdown_menu_opened, num_sec=10, delay=0.2, message="widget dropdown menu closed") def drag_and_drop(self, widget): sel.drag_and_drop(self.drag_element, widget.drop_element)
class Snapshot(object): snapshot_tree = deferred_verpick({ version.LOWEST: Tree("//div[@id='snapshots_treebox']/ul"), '5.7.0.1': BootstrapTreeview('snapshot_treebox') }) def __init__(self, name=None, description=None, memory=None, parent_vm=None): super(Vm.Snapshot, self).__init__() self.name = name self.description = description self.memory = memory self.vm = parent_vm def _nav_to_snapshot_mgmt(self): snapshot_title = '"Snapshots" for Virtual Machine "{}"'.format( self.vm.name) if summary_title() != snapshot_title: self.vm.load_details() sel.click(InfoBlock.element("Properties", "Snapshots")) def does_snapshot_exist(self): self._nav_to_snapshot_mgmt() try: if self.name is not None: self.snapshot_tree.find_path_to( re.compile(self.name + r".*?")) else: self.snapshot_tree.find_path_to( re.compile(self.description + r".*?")) return True except CandidateNotFound: return False except NoSuchElementException: return False def wait_for_snapshot_active(self): self._nav_to_snapshot_mgmt() try: self.snapshot_tree.click_path( *self.snapshot_tree.find_path_to(re.compile(self.name))) if sel.is_displayed_text(self.name + " (Active)"): return True except CandidateNotFound: return False def create(self): self._nav_to_snapshot_mgmt() toolbar.select('Create a new snapshot for this VM') if self.name is not None: fill(snapshot_form, { 'name': self.name, 'description': self.description, 'snapshot_memory': self.memory }, action=snapshot_form.create_button) else: fill(snapshot_form, { 'description': self.description, 'snapshot_memory': self.memory }, action=snapshot_form.create_button) wait_for(self.does_snapshot_exist, num_sec=300, delay=20, fail_func=sel.refresh, handle_exception=True) def delete(self, cancel=False): self._nav_to_snapshot_mgmt() toolbar.select('Delete Snapshots', 'Delete Selected Snapshot', invokes_alert=True) sel.handle_alert(cancel=cancel) if not cancel: flash.assert_message_match( 'Remove Snapshot initiated for 1 ' 'VM and Instance from the CFME Database') def delete_all(self, cancel=False): self._nav_to_snapshot_mgmt() toolbar.select('Delete Snapshots', 'Delete All Existing Snapshots', invokes_alert=True) sel.handle_alert(cancel=cancel) if not cancel: flash.assert_message_match( 'Remove All Snapshots initiated for 1 VM and ' 'Instance from the CFME Database') def revert_to(self, cancel=False): self._nav_to_snapshot_mgmt() self.snapshot_tree.click_path( *self.snapshot_tree.find_path_to(re.compile(self.name))) toolbar.select('Revert to selected snapshot', invokes_alert=True) sel.handle_alert(cancel=cancel) flash.assert_message_match( 'Revert To Snapshot initiated for 1 VM and Instance from ' 'the CFME Database')
class AzureInstance(Instance): # CFME & provider power control options Added by Jeff Teehan on 5-16-2016 START = "Start" POWER_ON = START # For compatibility with the infra objects. STOP = "Stop" SUSPEND = "Suspend" DELETE = "Delete" TERMINATE = deferred_verpick({ version.LOWEST: 'Terminate', '5.6.1': 'Delete', }) # CFME-only power control options SOFT_REBOOT = "Soft Reboot" # Provider-only power control options RESTART = "Restart" # CFME power states STATE_ON = "on" STATE_OFF = "off" STATE_SUSPENDED = "suspended" STATE_TERMINATED = "terminated" STATE_UNKNOWN = "unknown" STATE_ARCHIVED = "archived" @property def ui_powerstates_available(self): return { 'on': [self.STOP, self.SUSPEND, self.SOFT_REBOOT, self.TERMINATE], 'off': [self.START, self.TERMINATE] } @property def ui_powerstates_unavailable(self): return { 'on': [self.START], 'off': [self.STOP, self.SUSPEND, self.SOFT_REBOOT] } def create(self, cancel=False, **prov_fill_kwargs): """Provisions an Azure instance with the given properties through CFME Args: cancel: Clicks the cancel button if `True`, otherwise clicks the submit button (Defaults to `False`) prov_fill_kwargs: dictionary of provisioning field/value pairs Note: For more optional keyword arguments, see :py:data:`cfme.cloud.provisioning.ProvisioningForm` """ super(AzureInstance, self).create(form_values=prov_fill_kwargs, cancel=cancel) def power_control_from_provider(self, option): """Power control the instance from the provider Args: option: power control action to take against instance Raises: OptionNotAvailable: option param must have proper value """ if option == AzureInstance.START: self.provider.mgmt.start_vm(self.name) elif option == AzureInstance.STOP: self.provider.mgmt.stop_vm(self.name) elif option == AzureInstance.RESTART: self.provider.mgmt.restart_vm(self.name) elif option == AzureInstance.SUSPEND: self.provider.mgmt.suspend_vm(self.name) elif option == AzureInstance.TERMINATE: self.provider.mgmt.delete_vm(self.name) else: raise OptionNotAvailable(option + " is not a supported action")
class Genealogy(object): """Class, representing genealogy of an infra object with possibility of data retrieval and comparison. Args: o: The :py:class:`Vm` or :py:class:`Template` object. """ genealogy_tree = deferred_verpick({ version.LOWEST: CheckboxTree("//div[@id='genealogy_treebox']/ul"), 5.7: BootstrapTreeview('genealogy_treebox') }) section_comparison_tree = CheckboxTree( "//div[@id='all_sections_treebox']/div/table") apply_button = form_buttons.FormButton("Apply sections") mode_mapping = { "exists": "Exists Mode", "details": "Details Mode", } attr_mapping = { "all": "All Attributes", "different": "Attributes with different values", "same": "Attributes with same values", } def __init__(self, o): self.o = o def navigate(self): self.o.load_details() sel.click(InfoBlock.element("Relationships", "Genealogy")) def compare(self, *objects, **kwargs): """Compares two or more objects in the genealogy. Args: *objects: :py:class:`Vm` or :py:class:`Template` or :py:class:`str` with name. Keywords: sections: Which sections to compare. attributes: `all`, `different` or `same`. Default: `all`. mode: `exists` or `details`. Default: `exists`.""" sections = kwargs.get("sections") attributes = kwargs.get("attributes", "all").lower() mode = kwargs.get("mode", "exists").lower() assert len(objects) >= 2, "You must specify at least two objects" objects = map(lambda o: o.name if isinstance(o, (Vm, Template)) else o, objects) self.navigate() for obj in objects: if not isinstance(obj, list): path = self.genealogy_tree.find_path_to(obj) self.genealogy_tree.check_node(*path) toolbar.select("Compare selected VMs") # COMPARE PAGE flash.assert_no_errors() if sections is not None: map(lambda path: self.section_comparison_tree.check_node(*path), sections) sel.click(self.apply_button) flash.assert_no_errors() # Set requested attributes sets toolbar.select(self.attr_mapping[attributes]) # Set the requested mode toolbar.select(self.mode_mapping[mode]) @property def tree(self): """Returns contents of the tree with genealogy""" self.navigate() return self.genealogy_tree.read_contents() @property def ancestors(self): """Returns list of ancestors of the represented object.""" self.navigate() path = self.genealogy_tree.find_path_to( re.compile(r"^.*?\(Selected\)$")) if not path: raise ValueError("Something wrong happened, path not found!") processed_path = [] for step in path[:-1]: # We will remove the (parent) and (Selected) suffixes processed_path.append( re.sub(r"\s*(?:\(Current\)|\(Parent\))$", "", step)) return processed_path