class BaseCondition(Updateable, Navigatable, Pretty): TREE_NODE = None PRETTY = None FIELD_VALUE = None _param_name = ParamClassName('description') def __init__(self, description, expression=None, scope=None, notes=None, appliance=None): Navigatable.__init__(self, appliance=appliance) self.description = description self.expression = expression self.scope = scope self.notes = notes def create(self): view = navigate_to(self, "Add") view.fill({ "description": self.description, "expression": self.expression, "scope": self.scope, "notes": self.notes }) view.add_button.click() view = self.create_view(ConditionDetailsView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message('Condition "{}" was added'.format( self.description)) def update(self, updates): """Update this Condition in UI. Args: updates: Provided by update() context manager. cancel: Whether to cancel the update (default False). """ view = navigate_to(self, "Edit") view.fill(updates) view.save_button.click() view = self.create_view(ConditionDetailsView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message('Condition "{}" was saved'.format( updates.get("description", self.description))) def delete(self, cancel=False): """Delete this Condition in UI. Args: cancel: Whether to cancel the deletion (default False). """ view = navigate_to(self, "Details") view.configuration.item_select("Delete this {} Condition".format( self.FIELD_VALUE), handle_alert=not cancel) if cancel: assert view.is_displayed view.flash.assert_no_error() else: view = self.create_view(ConditionsAllView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message( 'Condition "{}": Delete successful'.format(self.description)) def read_expression(self): view = navigate_to(self, "Details") assert view.is_displayed return view.expression.read() def read_scope(self): view = navigate_to(self, "Details") assert view.is_displayed return view.scope.read() @property def exists(self): """Check existence of this Condition. Returns: :py:class:`bool` signalizing the presence of the Condition in the database. """ conditions = self.appliance.db["conditions"] return self.appliance.db.session\ .query(conditions.description)\ .filter(conditions.description == self.description)\ .count() > 0
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 BaseProvider(Taggable, Updateable, SummaryMixin, Navigatable): # List of constants that every non-abstract subclass must have defined _param_name = ParamClassName('name') STATS_TO_MATCH = [] string_name = "" page_name = "" edit_page_suffix = "" detail_page_suffix = "" refresh_text = "" quad_name = None _properties_form = None _properties_region = None add_provider_button = None save_button = None db_types = ["Providers"] def __hash__(self): return hash(self.key) ^ hash(type(self)) def __eq__(self, other): return type(self) is type(other) and self.key == other.key @property def properties_form(self): if self._properties_region: return self._properties_region.properties_form else: return self._properties_form @property def data(self): return self.get_yaml_data() @property def mgmt(self): return self.get_mgmt_system() @property def type(self): return self.type_name @property def version(self): return self.data['version'] def deployment_helper(self, deploy_args): """ Used in utils.virtual_machines and usually overidden""" return {} @property def default_endpoint(self): return self.endpoints.get('default') if hasattr(self, 'endpoints') else None def get_yaml_data(self): """ Returns yaml data for this provider. """ if hasattr(self, 'provider_data') and self.provider_data is not None: return self.provider_data elif self.key is not None: return conf.cfme_data['management_systems'][self.key] else: raise ProviderHasNoKey( 'Provider {} has no key, so cannot get yaml data'.format( self.name)) def get_mgmt_system(self): """ Returns the mgmt_system using the :py:func:`utils.providers.get_mgmt` method. """ # gotta stash this in here to prevent circular imports from utils.providers import get_mgmt if self.key: return get_mgmt(self.key) elif getattr(self, 'provider_data', None): return get_mgmt(self.provider_data) else: raise ProviderHasNoKey( 'Provider {} has no key, so cannot get mgmt system'.format( self.name)) def _submit(self, cancel, submit_button): if cancel: form_buttons.cancel() # sel.wait_for_element(page.configuration_btn) else: submit_button() flash.assert_no_errors() def create(self, cancel=False, validate_credentials=True, check_existing=False, validate_inventory=False): """ Creates a provider in the UI Args: cancel (boolean): Whether to cancel out of the creation. The cancel is done after all the information present in the Provider 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. check_existing (boolean): Check if this provider already exists, skip if it does validate_inventory (boolean): Whether or not to block until the provider stats in CFME match the stats gleaned from the backend management system (default: ``True``) Returns: True if it was created, False if it already existed """ from cfme.infrastructure.provider import InfraProvider from cfme.cloud.provider import CloudProvider if self.one_of(CloudProvider, InfraProvider): if check_existing and self.exists: created = False else: created = True logger.info('Setting up Infra Provider: %s', self.key) add_view = navigate_to(self, 'Add') if not cancel or (cancel and any(self.view_value_mapping.values())): # filling main part of dialog add_view.fill(self.view_value_mapping) if not cancel or (cancel and self.endpoints): # filling endpoints for endpoint_name, endpoint in self.endpoints.items(): try: # every endpoint class has name like 'default', 'events', etc. # endpoints view can have multiple tabs, the code below tries # to find right tab by passing endpoint name to endpoints view endp_view = getattr( self.endpoints_form(parent=add_view), endpoint_name) except AttributeError: # tabs are absent in UI when there is only single (default) endpoint endp_view = self.endpoints_form(parent=add_view) endp_view.fill(endpoint.view_value_mapping) # filling credentials if hasattr(endpoint, 'credentials'): endp_view.fill( endpoint.credentials.view_value_mapping) if validate_credentials and hasattr( endp_view, 'validate'): # there are some endpoints which don't demand validation like # RSA key pair endp_view.validate.click() if self.one_of(InfraProvider): main_view_obj = InfraProvidersView elif self.one_of(CloudProvider): main_view_obj = CloudProvidersView main_view = self.create_view(main_view_obj) if cancel: created = False add_view.cancel.click() cancel_text = ('Add of {} Provider was ' 'cancelled by the user'.format( self.string_name)) main_view.entities.flash.assert_message(cancel_text) main_view.entities.flash.assert_no_error() else: add_view.add.click() if main_view.is_displayed: success_text = '{} Providers "{}" was saved'.format( self.string_name, self.name) main_view.entities.flash.assert_message(success_text) else: add_view.flash.assert_no_error() raise AssertionError( "Provider wasn't added. It seems form isn't accurately" " filled") if validate_inventory: self.validate() return created else: # other providers, old code if check_existing and self.exists: created = False else: created = True logger.info('Setting up provider: %s', self.key) navigate_to(self, 'Add') fill(self.properties_form, self._form_mapping(True, **self.__dict__)) for cred in self.credentials: fill(self.credentials[cred].form, self.credentials[cred], validate=validate_credentials) self._submit(cancel, self.add_provider_button) if not cancel: flash.assert_message_match('{} Providers ' '"{}" was saved'.format( self.string_name, self.name)) if validate_inventory: self.validate() return created def update(self, updates, cancel=False, validate_credentials=True): """ Updates a provider 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. validate_credentials (boolean): whether credentials have to be validated """ from cfme.infrastructure.provider import InfraProvider from cfme.cloud.provider import CloudProvider if self.one_of(CloudProvider, InfraProvider): edit_view = navigate_to(self, 'Edit') # todo: to replace/merge this code with create # update values: # filling main part of dialog endpoints = updates.pop('endpoints', None) if updates: edit_view.fill(updates) # filling endpoints if endpoints: endpoints = self._prepare_endpoints(endpoints) for endpoint in endpoints.values(): # every endpoint class has name like 'default', 'events', etc. # endpoints view can have multiple tabs, the code below tries # to find right tab by passing endpoint name to endpoints view try: endp_view = getattr( self.endpoints_form(parent=edit_view), endpoint.name) except AttributeError: # tabs are absent in UI when there is only single (default) endpoint endp_view = self.endpoints_form(parent=edit_view) endp_view.fill(endpoint.view_value_mapping) # filling credentials # the code below looks for existing endpoint equal to passed one and # compares their credentials. it fills passed credentials # if credentials are different cur_endpoint = self.endpoints[endpoint.name] if hasattr(endpoint, 'credentials'): if not hasattr(cur_endpoint, 'credentials') or \ endpoint.credentials != cur_endpoint.credentials: if hasattr(endp_view, 'change_password'): endp_view.change_password.click() elif hasattr(endp_view, 'change_key'): endp_view.change_key.click() else: NotImplementedError( "Such endpoint doesn't have change password/key button" ) endp_view.fill( endpoint.credentials.view_value_mapping) if validate_credentials: endp_view.validate.click() # cloud rhos provider always requires validation of all endpoints # there should be a bz about that from cfme.cloud.provider.openstack import OpenStackProvider if self.one_of(OpenStackProvider): for endp in self.endpoints.values(): endp_view = getattr(self.endpoints_form(parent=edit_view), endp.name) endp_view.validate.click() if self.one_of(InfraProvider): details_view_obj = InfraProviderDetailsView main_view_obj = InfraProvidersView elif self.one_of(CloudProvider): details_view_obj = CloudProviderDetailsView main_view_obj = CloudProvidersView details_view = self.create_view(details_view_obj) main_view = self.create_view(main_view_obj) if cancel: edit_view.cancel.click() cancel_text = 'Edit of {type} Provider "{name}" ' \ 'was cancelled by the user'.format(type=self.string_name, name=self.name) main_view.entities.flash.assert_message(cancel_text) main_view.entities.flash.assert_no_error() else: edit_view.save.click() if endpoints: for endp_name, endp in endpoints.items(): self.endpoints[endp_name] = endp if updates: self.name = updates.get('name', self.name) if BZ.bugzilla.get_bug( 1436341 ).is_opened and version.current_version() > '5.8': logger.warning( 'Skipping flash message verification because of BZ 1436341' ) return success_text = '{} Provider "{}" was saved'.format( self.string_name, self.name) if main_view.is_displayed: # since 5.8.1 main view is displayed when edit starts from main view main_view.flash.assert_message(success_text) elif details_view.is_displayed: # details view is always displayed up to 5.8.1 details_view.flash.assert_message(success_text) else: edit_view.flash.assert_no_error() raise AssertionError( "Provider wasn't updated. It seems form isn't accurately" " filled") else: # other providers, old code navigate_to(self, 'Edit') fill(self.properties_form, self._form_mapping(**updates)) for cred in self.credentials: fill(self.credentials[cred].form, updates.get('credentials', {}).get(cred), validate=validate_credentials) self._submit(cancel, self.save_button) name = updates.get('name', self.name) if not cancel: if BZ.bugzilla.get_bug( 1436341 ).is_opened and version.current_version() > '5.8': logger.warning( 'Skipping flash message verification because of BZ 1436341' ) return flash.assert_message_match('{} Provider "{}" was saved'.format( self.string_name, name)) def delete(self, cancel=True): """ Deletes a provider from CFME Args: cancel: Whether to cancel the deletion, defaults to True """ self.load_details() cfg_btn('Remove this {} Provider'.format(self.string_name), invokes_alert=True) sel.handle_alert(cancel=cancel) if not cancel: flash.assert_message_match( 'Delete initiated for 1 {} Provider from the {} Database'. format(self.string_name, self.appliance.product_name)) def setup(self): """ Sets up the provider robustly """ return self.create(cancel=False, validate_credentials=True, check_existing=True, validate_inventory=True) def delete_if_exists(self, *args, **kwargs): """Combines ``.exists`` and ``.delete()`` as a shortcut for ``request.addfinalizer`` Returns: True if provider existed and delete was initiated, False otherwise """ if self.exists: self.delete(*args, **kwargs) return True return False @variable(alias='rest') def is_refreshed(self, refresh_timer=None): if refresh_timer: if refresh_timer.is_it_time(): logger.info(' Time for a refresh!') self.refresh_provider_relationships() refresh_timer.reset() rdate = self.last_refresh_date() if not rdate: return False td = self.appliance.utc_time() - rdate if td > datetime.timedelta(0, 600): self.refresh_provider_relationships() return False else: return True def validate(self): refresh_timer = RefreshTimer(time_for_refresh=300) try: wait_for(self.is_refreshed, [refresh_timer], message="is_refreshed", num_sec=1000, delay=60, handle_exception=True) except Exception: # To see the possible error. self.load_details(refresh=True) raise def validate_stats(self, ui=False): """ Validates that the detail page matches the Providers information. This method logs into the provider using the mgmt_system interface and collects a set of statistics to be matched against the UI. The details page is then refreshed continuously until the matching of all items is complete. A error will be raised if the match is not complete within a certain defined time period. """ # If we're not using db, make sure we are on the provider detail page if ui: self.load_details() # Initial bullet check if self._do_stats_match(self.mgmt, self.STATS_TO_MATCH, ui=ui): self.mgmt.disconnect() return else: # Set off a Refresh Relationships method = 'ui' if ui else None self.refresh_provider_relationships(method=method) refresh_timer = RefreshTimer(time_for_refresh=300) wait_for(self._do_stats_match, [self.mgmt, self.STATS_TO_MATCH, refresh_timer], {'ui': ui}, message="do_stats_match_db", num_sec=1000, delay=60) self.mgmt.disconnect() @variable(alias='rest') def refresh_provider_relationships(self, from_list_view=False): # from_list_view is ignored as it is included here for sake of compatibility with UI call. logger.debug('Refreshing provider relationships') col = self.appliance.rest_api.collections.providers.find_by( name=self.name) try: col[0].action.refresh() except IndexError: raise Exception("Provider collection empty") @refresh_provider_relationships.variant('ui') def refresh_provider_relationships_ui(self, from_list_view=False): """Clicks on Refresh relationships button in provider""" if from_list_view: navigate_to(self, 'All') sel.check(Quadicon(self.name, self.quad_name).checkbox()) else: navigate_to(self, 'Details') tb.select("Configuration", self.refresh_text, invokes_alert=True) sel.handle_alert(cancel=False) @variable(alias='rest') def last_refresh_date(self): try: col = self.appliance.rest_api.collections.providers.find_by( name=self.name)[0] return col.last_refresh_date except AttributeError: return None def _num_db_generic(self, table_str): """ Fetch number of rows related to this provider in a given table Args: table_str: Name of the table; e.g. 'vms' or 'hosts' """ res = self.appliance.db.client.engine.execute( "SELECT count(*) " "FROM ext_management_systems, {0} " "WHERE {0}.ems_id=ext_management_systems.id " "AND ext_management_systems.name='{1}'".format( table_str, self.name)) return int(res.first()[0]) def _do_stats_match(self, client, stats_to_match=None, refresh_timer=None, ui=False): """ A private function to match a set of statistics, with a Provider. This function checks if the list of stats match, if not, the page is refreshed. Note: Provider mgmt_system uses the same key names as this Provider class to avoid having to map keyname/attributes e.g. ``num_template``, ``num_vm``. Args: client: A provider mgmt_system instance. stats_to_match: A list of key/attribute names to match. Raises: KeyError: If the host stats does not contain the specified key. ProviderHasNoProperty: If the provider does not have the property defined. """ host_stats = client.stats(*stats_to_match) method = None if ui: sel.refresh() method = 'ui' if refresh_timer: if refresh_timer.is_it_time(): logger.info(' Time for a refresh!') self.refresh_provider_relationships() refresh_timer.reset() for stat in stats_to_match: try: cfme_stat = getattr(self, stat)(method=method) success, value = tol_check(host_stats[stat], cfme_stat, min_error=0.05, low_val_correction=2) logger.info( ' Matching stat [%s], Host(%s), CFME(%s), ' 'with tolerance %s is %s', stat, host_stats[stat], cfme_stat, value, success) if not success: return False except KeyError: raise HostStatsNotContains( "Host stats information does not contain '{}'".format( stat)) except AttributeError: raise ProviderHasNoProperty( "Provider does not know how to get '{}'".format(stat)) else: return True @property def exists(self): """ Returns ``True`` if a provider of the same name exists on the appliance """ if self.name in self.appliance.managed_provider_names: return True return False def wait_for_delete(self): navigate_to(self, 'All') quad = Quadicon(self.name, self.quad_name) logger.info('Waiting for a provider to delete...') wait_for(lambda prov: not sel.is_displayed(prov), func_args=[quad], fail_condition=False, message="Wait provider to disappear", num_sec=1000, fail_func=sel.refresh) def _on_detail_page(self): """ Returns ``True`` if on the providers detail page, ``False`` if not.""" if not self.string_name: # No point in doing that since it is probably being called from badly configured class # And since it is badly configured, let's notify the user. logger.warning( 'Hey, _on_details_page called from {} class which does not have string_name set' .format(type(self).__name__)) return False ensure_browser_open() collection = '{} Providers'.format(self.string_name) title = '{} (Summary)'.format(self.name) return breadcrumbs_names() == [collection, title ] and summary_title() == title def load_details(self, refresh=False): """To be compatible with the Taggable and PolicyProfileAssignable mixins.""" navigate_to(self, 'Details') if refresh: tb.refresh() def get_detail(self, *ident, **kwargs): """ Gets details from the details infoblock The function first ensures that we are on the detail page for the specific provider. Args: *ident: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Keywords: use_icon: Whether to use icon matching Returns: A string representing the contents of the InfoBlock's value. """ self.load_details() if kwargs.get("use_icon", False): title, icon = ident return details_page.infoblock(title).by_member_icon(icon).text else: return details_page.infoblock.text(*ident) @classmethod def get_credentials(cls, credential_dict, cred_type=None): """Processes a credential dictionary into a credential object. Args: credential_dict: A credential dictionary. cred_type: Type of credential (None, token, ssh, amqp, ...) Returns: A :py:class:`cfme.base.credential.Credential` instance. """ domain = credential_dict.get('domain') token = credential_dict.get('token') if not cred_type: return Credential(principal=credential_dict['username'], secret=credential_dict['password'], domain=domain) elif cred_type == 'amqp': return EventsCredential(principal=credential_dict['username'], secret=credential_dict['password']) elif cred_type == 'ssh': return SSHCredential(principal=credential_dict['username'], secret=credential_dict['password']) elif cred_type == 'candu': return CANDUCredential(principal=credential_dict['username'], secret=credential_dict['password']) elif cred_type == 'token': return TokenCredential(token=token) @classmethod def get_credentials_from_config(cls, credential_config_name, cred_type=None): """Retrieves the credential by its name from the credentials yaml. Args: credential_config_name: The name of the credential in the credentials yaml. cred_type: Type of credential (None, token, ssh, amqp, ...) Returns: A :py:class:`cfme.base.credential.Credential` instance. """ creds = conf.credentials[credential_config_name] return cls.get_credentials(creds, cred_type=cred_type) @classmethod def process_credential_yaml_key(cls, cred_yaml_key, cred_type=None): """Function that detects if it needs to look up credentials in the credential yaml and acts as expected. If you pass a dictionary, it assumes it does not need to look up in the credentials yaml file. If anything else is passed, it continues with looking up the credentials in the yaml file. Args: cred_yaml_key: Either a string pointing to the credentials.yaml or a dictionary which is considered as the credentials. Returns: :py:class:`cfme.base.credential.Credential` instance """ if isinstance(cred_yaml_key, dict): return cls.get_credentials(cred_yaml_key, cred_type=cred_type) else: return cls.get_credentials_from_config(cred_yaml_key, cred_type=cred_type) # Move to collection @classmethod def clear_providers(cls): """ Clear all providers of given class on the appliance """ from utils.appliance import current_appliance as app app.rest_api.collections.providers.reload() for prov in app.rest_api.collections.providers.all: try: if any( [True for db_type in cls.db_types if db_type in prov.type]): logger.info('Deleting provider: %s', prov.name) prov.action.delete() prov.wait_not_exists() except APIException as ex: # Provider is already gone (usually caused by NetworkManager objs) if 'RecordNotFound' not in str(ex): raise ex app.rest_api.collections.providers.reload() def one_of(self, *classes): """ Returns true if provider is an instance of any of the classes or sublasses there of""" return isinstance(self, classes) @staticmethod def _prepare_endpoints(endpoints): if not endpoints: return {} elif isinstance(endpoints, dict): return endpoints elif isinstance(endpoints, Iterable): return {(e.name, e) for e in endpoints} elif isinstance(endpoints, DefaultEndpoint): return {endpoints.name: endpoints} else: raise ValueError( "Endpoints should be either dict or endpoint class")
class BaseProvider(Taggable, Updateable, SummaryMixin, Navigatable): # List of constants that every non-abstract subclass must have defined _param_name = ParamClassName('name') STATS_TO_MATCH = [] string_name = "" page_name = "" edit_page_suffix = "" detail_page_suffix = "" refresh_text = "" quad_name = None _properties_form = None _properties_region = None add_provider_button = None save_button = None db_types = ["Providers"] class Credential(cfme.Credential, Updateable): """Provider credentials Args: type: One of [amqp, candu, ssh, token] (optional) domain: Domain for default credentials (optional) """ @property def form(self): fields = [ ('token_secret_55', Input('bearer_token')), ('google_service_account', Input('service_account')), ] tab_fields = { ("Default", ('default_when_no_tabs', )): [ ('default_principal', Input("default_userid")), ('default_secret', Input("default_password")), ('default_verify_secret', Input("default_verify")), ('token_secret', { version.LOWEST: Input('bearer_password'), '5.6': Input('default_password') }), ('token_verify_secret', { version.LOWEST: Input('bearer_verify'), '5.6': Input('default_verify') }), ], "RSA key pair": [ ('ssh_user', Input("ssh_keypair_userid")), ('ssh_key', FileInput("ssh_keypair_password")), ], "C & U Database": [ ('candu_principal', Input("metrics_userid")), ('candu_secret', Input("metrics_password")), ('candu_verify_secret', Input("metrics_verify")), ], "Hawkular": [ ('hawkular_validate_btn', form_buttons.validate), ] } fields_end = [ ('validate_btn', form_buttons.validate), ] if version.current_version() >= '5.6': amevent = "Events" else: amevent = "AMQP" tab_fields[amevent] = [] if version.current_version() >= "5.6": tab_fields[amevent].append( ('event_selection', Radio('event_stream_selection'))) tab_fields[amevent].extend([ ('amqp_principal', Input("amqp_userid")), ('amqp_secret', Input("amqp_password")), ('amqp_verify_secret', Input("amqp_verify")), ]) return TabStripForm(fields=fields, tab_fields=tab_fields, fields_end=fields_end) def __init__(self, **kwargs): super(BaseProvider.Credential, self).__init__(**kwargs) self.type = kwargs.get('cred_type', None) self.domain = kwargs.get('domain', None) if self.type == 'token': self.token = kwargs['token'] if self.type == 'service_account': self.service_account = kwargs['service_account'] def __hash__(self): return hash(self.key) ^ hash(type(self)) def __eq__(self, other): return type(self) is type(other) and self.key == other.key @property def properties_form(self): if self._properties_region: return self._properties_region.properties_form else: return self._properties_form @property def data(self): return self.get_yaml_data() @property def mgmt(self): return self.get_mgmt_system() @property def type(self): return self.type_name @property def version(self): return self.data['version'] def deployment_helper(self, deploy_args): """ Used in utils.virtual_machines and usually overidden""" return {} def get_yaml_data(self): """ Returns yaml data for this provider. """ if hasattr(self, 'provider_data') and self.provider_data is not None: return self.provider_data elif self.key is not None: return conf.cfme_data['management_systems'][self.key] else: raise ProviderHasNoKey( 'Provider {} has no key, so cannot get yaml data'.format( self.name)) def get_mgmt_system(self): """ Returns the mgmt_system using the :py:func:`utils.providers.get_mgmt` method. """ # gotta stash this in here to prevent circular imports from utils.providers import get_mgmt if self.key: return get_mgmt(self.key) elif getattr(self, 'provider_data', None): return get_mgmt(self.provider_data) else: raise ProviderHasNoKey( 'Provider {} has no key, so cannot get mgmt system'.format( self.name)) def _submit(self, cancel, submit_button): if cancel: form_buttons.cancel() # sel.wait_for_element(page.configuration_btn) else: submit_button() flash.assert_no_errors() def create(self, cancel=False, validate_credentials=True, check_existing=False, validate_inventory=False): """ Creates a provider in the UI Args: cancel (boolean): Whether to cancel out of the creation. The cancel is done after all the information present in the Provider 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. check_existing (boolean): Check if this provider already exists, skip if it does validate_inventory (boolean): Whether or not to block until the provider stats in CFME match the stats gleaned from the backend management system (default: ``True``) Returns: True if it was created, False if it already existed """ if check_existing and self.exists: created = False else: created = True logger.info('Setting up provider: %s', self.key) navigate_to(self, 'Add') fill(self.properties_form, self._form_mapping(True, hawkular=False, **self.__dict__)) for cred in self.credentials: fill(self.credentials[cred].form, self.credentials[cred], validate=validate_credentials) self._submit(cancel, self.add_provider_button) if not cancel: flash.assert_message_match( '{} Providers "{}" was saved'.format( self.string_name, self.name)) if validate_inventory: self.validate() return created def update(self, updates, cancel=False, validate_credentials=True): """ Updates a provider 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') fill(self.properties_form, self._form_mapping(**updates)) for cred in self.credentials: fill(self.credentials[cred].form, updates.get('credentials', {}).get(cred, None), validate=validate_credentials) self._submit(cancel, self.save_button) name = updates.get('name', self.name) if not cancel: flash.assert_message_match('{} Provider "{}" was saved'.format( self.string_name, name)) def delete(self, cancel=True): """ Deletes a provider from CFME Args: cancel: Whether to cancel the deletion, defaults to True """ self.load_details() cfg_btn('Remove this {} Provider'.format(self.string_name), invokes_alert=True) sel.handle_alert(cancel=cancel) if not cancel: flash.assert_message_match( 'Delete initiated for 1 {} Provider from the {} Database'. format(self.string_name, self.appliance.product_name)) def setup(self): """ Sets up the provider robustly """ return self.create(cancel=False, validate_credentials=True, check_existing=True, validate_inventory=True) def delete_if_exists(self, *args, **kwargs): """Combines ``.exists`` and ``.delete()`` as a shortcut for ``request.addfinalizer`` Returns: True if provider existed and delete was initiated, False otherwise """ if self.exists: self.delete(*args, **kwargs) return True return False @variable(alias='rest') def is_refreshed(self, refresh_timer=None): if refresh_timer: if refresh_timer.is_it_time(): logger.info(' Time for a refresh!') self.refresh_provider_relationships() refresh_timer.reset() rdate = self.last_refresh_date() if not rdate: return False td = self.appliance.utc_time() - rdate if td > datetime.timedelta(0, 600): self.refresh_provider_relationships() return False else: return True def validate(self): refresh_timer = RefreshTimer(time_for_refresh=300) try: wait_for(self.is_refreshed, [refresh_timer], message="is_refreshed", num_sec=1000, delay=60, handle_exception=True) except Exception: # To see the possible error. self.load_details(refresh=True) raise def validate_stats(self, ui=False): """ Validates that the detail page matches the Providers information. This method logs into the provider using the mgmt_system interface and collects a set of statistics to be matched against the UI. The details page is then refreshed continuously until the matching of all items is complete. A error will be raised if the match is not complete within a certain defined time period. """ # If we're not using db, make sure we are on the provider detail page if ui: self.load_details() # Initial bullet check if self._do_stats_match(self.mgmt, self.STATS_TO_MATCH, ui=ui): self.mgmt.disconnect() return else: # Set off a Refresh Relationships method = 'ui' if ui else None self.refresh_provider_relationships(method=method) refresh_timer = RefreshTimer(time_for_refresh=300) wait_for(self._do_stats_match, [self.mgmt, self.STATS_TO_MATCH, refresh_timer], {'ui': ui}, message="do_stats_match_db", num_sec=1000, delay=60) self.mgmt.disconnect() @variable(alias='rest') def refresh_provider_relationships(self, from_list_view=False): # from_list_view is ignored as it is included here for sake of compatibility with UI call. logger.debug('Refreshing provider relationships') col = self.appliance.rest_api.collections.providers.find_by( name=self.name) try: col[0].action.refresh() except IndexError: raise Exception("Provider collection empty") @refresh_provider_relationships.variant('ui') def refresh_provider_relationships_ui(self, from_list_view=False): """Clicks on Refresh relationships button in provider""" if from_list_view: navigate_to(self, 'All') sel.check(Quadicon(self.name, self.quad_name).checkbox()) else: navigate_to(self, 'Details') tb.select("Configuration", self.refresh_text, invokes_alert=True) sel.handle_alert(cancel=False) @variable(alias='rest') def last_refresh_date(self): try: col = self.appliance.rest_api.collections.providers.find_by( name=self.name)[0] return col.last_refresh_date except AttributeError: return None def _num_db_generic(self, table_str): """ Fetch number of rows related to this provider in a given table Args: table_str: Name of the table; e.g. 'vms' or 'hosts' """ res = self.appliance.db.engine.execute( "SELECT count(*) " "FROM ext_management_systems, {0} " "WHERE {0}.ems_id=ext_management_systems.id " "AND ext_management_systems.name='{1}'".format( table_str, self.name)) return int(res.first()[0]) def _do_stats_match(self, client, stats_to_match=None, refresh_timer=None, ui=False): """ A private function to match a set of statistics, with a Provider. This function checks if the list of stats match, if not, the page is refreshed. Note: Provider mgmt_system uses the same key names as this Provider class to avoid having to map keyname/attributes e.g. ``num_template``, ``num_vm``. Args: client: A provider mgmt_system instance. stats_to_match: A list of key/attribute names to match. Raises: KeyError: If the host stats does not contain the specified key. ProviderHasNoProperty: If the provider does not have the property defined. """ host_stats = client.stats(*stats_to_match) method = None if ui: sel.refresh() method = 'ui' if refresh_timer: if refresh_timer.is_it_time(): logger.info(' Time for a refresh!') self.refresh_provider_relationships() refresh_timer.reset() for stat in stats_to_match: try: cfme_stat = getattr(self, stat)(method=method) success, value = tol_check(host_stats[stat], cfme_stat, min_error=0.05, low_val_correction=2) logger.info( ' Matching stat [%s], Host(%s), CFME(%s), ' 'with tolerance %s is %s', stat, host_stats[stat], cfme_stat, value, success) if not success: return False except KeyError: raise HostStatsNotContains( "Host stats information does not contain '{}'".format( stat)) except AttributeError: raise ProviderHasNoProperty( "Provider does not know how to get '{}'".format(stat)) else: return True @property def exists(self): """ Returns ``True`` if a provider of the same name exists on the appliance """ if self.name in self.appliance.managed_provider_names: return True return False def wait_for_delete(self): navigate_to(self, 'All') quad = Quadicon(self.name, self.quad_name) logger.info('Waiting for a provider to delete...') wait_for(lambda prov: not sel.is_displayed(prov), func_args=[quad], fail_condition=False, message="Wait provider to disappear", num_sec=1000, fail_func=sel.refresh) def _on_detail_page(self): """ Returns ``True`` if on the providers detail page, ``False`` if not.""" if not self.string_name: # No point in doing that since it is probably being called from badly configured class # And since it is badly configured, let's notify the user. logger.warning( 'Hey, _on_details_page called from {} class which does not have string_name set' .format(type(self).__name__)) return False ensure_browser_open() collection = '{} Providers'.format(self.string_name) title = '{} (Summary)'.format(self.name) return breadcrumbs_names() == [collection, title ] and summary_title() == title def load_details(self, refresh=False): """To be compatible with the Taggable and PolicyProfileAssignable mixins.""" navigate_to(self, 'Details') if refresh: tb.refresh() def get_detail(self, *ident, **kwargs): """ Gets details from the details infoblock The function first ensures that we are on the detail page for the specific provider. Args: *ident: An InfoBlock title, followed by the Key name, e.g. "Relationships", "Images" Keywords: use_icon: Whether to use icon matching Returns: A string representing the contents of the InfoBlock's value. """ self.load_details() if kwargs.get("use_icon", False): title, icon = ident return details_page.infoblock(title).by_member_icon(icon).text else: return details_page.infoblock.text(*ident) @classmethod def get_credentials(cls, credential_dict, cred_type=None): """Processes a credential dictionary into a credential object. Args: credential_dict: A credential dictionary. cred_type: Type of credential (None, token, ssh, amqp, ...) Returns: A :py:class:`BaseProvider.Credential` instance. """ domain = credential_dict.get('domain', None) token = credential_dict.get('token', None) return cls.Credential(principal=credential_dict['username'], secret=credential_dict['password'], cred_type=cred_type, domain=domain, token=token) @classmethod def get_credentials_from_config(cls, credential_config_name, cred_type=None): """Retrieves the credential by its name from the credentials yaml. Args: credential_config_name: The name of the credential in the credentials yaml. cred_type: Type of credential (None, token, ssh, amqp, ...) Returns: A :py:class:`BaseProvider.Credential` instance. """ creds = conf.credentials[credential_config_name] return cls.get_credentials(creds, cred_type=cred_type) @classmethod def process_credential_yaml_key(cls, cred_yaml_key, cred_type=None): """Function that detects if it needs to look up credentials in the credential yaml and acts as expected. If you pass a dictionary, it assumes it does not need to look up in the credentials yaml file. If anything else is passed, it continues with looking up the credentials in the yaml file. Args: cred_yaml_key: Either a string pointing to the credentials.yaml or a dictionary which is considered as the credentials. Returns: :py:class:`BaseProvider.Credentials` instance """ if isinstance(cred_yaml_key, dict): return cls.get_credentials(cred_yaml_key, cred_type=cred_type) else: return cls.get_credentials_from_config(cred_yaml_key, cred_type=cred_type) # Move to collection @classmethod def clear_providers(cls): """ Clear all providers of given class on the appliance """ from utils.appliance import current_appliance as app app.rest_api.collections.providers.reload() for prov in app.rest_api.collections.providers.all: try: if any( [True for db_type in cls.db_types if db_type in prov.type]): logger.info('Deleting provider: %s', prov.name) prov.action.delete() prov.wait_not_exists() except APIException as ex: # Provider is already gone (usually caused by NetworkManager objs) if 'RecordNotFound' not in str(ex): raise ex app.rest_api.collections.providers.reload() def one_of(self, *classes): """ Returns true if provider is an instance of any of the classes or sublasses there of""" return isinstance(self, classes)
class BasePolicy(Updateable, Navigatable, Pretty): """This class represents a Policy. Example: .. code-block:: python >>> from cfme.control.explorer.policy import VMCompliancePolicy >>> policy = VMCompliancePolicy("policy_description") >>> policy.create() >>> policy.delete() Args: description: Policy name. active: Whether the policy active or not. scope: Policy scope. notes: Policy notes. """ TYPE = None TREE_NODE = None PRETTY = None _param_name = ParamClassName('description') def __init__(self, description, active=True, scope=None, notes=None, appliance=None): Navigatable.__init__(self, appliance=appliance) self.description = description self.active = active self.scope = scope self.notes = notes def __str__(self): return self.description def create(self): "Create this Policy in UI." view = navigate_to(self, "Add") view.fill({ "description": self.description, "active": self.active, "scope": self.scope, "notes": self.notes }) view.add_button.click() view = self.create_view(PolicyDetailsView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message('Policy "{}" was added'.format( self.description)) def update(self, updates): """Update this Policy in UI. Args: updates: Provided by update() context manager. cancel: Whether to cancel the update (default False). """ view = navigate_to(self, "Edit") changed = view.fill(updates) if changed: view.save_button.click() else: view.cancel_button.click() for attr, value in updates.items(): setattr(self, attr, value) view = self.create_view(PolicyDetailsView) assert view.is_displayed view.flash.assert_no_error() if changed: view.flash.assert_message('Policy "{}" was saved'.format( updates.get("description", self.description))) else: view.flash.assert_message( 'Edit of Policy "{}" was cancelled by the user'.format( self.description)) def delete(self, cancel=False): """Delete this Policy in UI. Args: cancel: Whether to cancel the deletion (default False). """ view = navigate_to(self, "Details") view.configuration.item_select("Delete this {} Policy".format( self.PRETTY), handle_alert=not cancel) if cancel: assert view.is_displayed view.flash.assert_no_error() else: view = self.create_view(PoliciesAllView) assert view.is_displayed view.flash.assert_no_error() def copy(self, cancel=False): """Copy this Policy in UI. Args: cancel: Whether to cancel the copying (default False). """ view = navigate_to(self, "Details") view.configuration.item_select("Copy this {} Policy".format( self.PRETTY), handle_alert=not cancel) view = self.create_view(PolicyDetailsView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message('Policy "Copy of {}" was added'.format( self.description)) return type(self)("Copy of {}".format(self.description)) def assign_events(self, *events, **kwargs): """Assign events to this Policy. Args: events: Events which need to be assigned. extend: Do not uncheck existing events. """ events = list(events) extend = kwargs.pop("extend", False) if extend: events += self.assigned_events view = navigate_to(self, "Details") view.configuration.item_select("Edit this Policy's Event assignments") view = self.create_view(EditPolicyEventAssignments) assert view.is_displayed changed = view.fill({"events": events}) if changed: view.save_button.click() else: view.cancel_button.click() view.flash.assert_no_error() view.flash.assert_message('Policy "{}" was saved'.format( self.description)) def is_event_assigned(self, event): return event in self.assigned_events def assign_conditions(self, *conditions): """Assign conditions to this Policy. Args: conditions: Conditions which need to be assigned. """ view = navigate_to(self, "Details") view.configuration.item_select( "Edit this Policy's Condition assignments") view = self.create_view(EditPolicyConditionAssignments) assert view.is_displayed changed = view.fill({ "conditions": [condition.description for condition in conditions] }) if changed: view.save_button.click() else: view.cancel_button.click() view.flash.assert_no_error() view.flash.assert_message('Policy "{}" was saved'.format( self.description)) def is_condition_assigned(self, condition): self.testing_condition = condition view = navigate_to(self, "Condition Details") return view.is_displayed def assign_actions_to_event(self, event, actions): """ This method takes a list or dict of actions, goes into the policy event and assigns them. Actions can be passed both as the objects, but they can be passed also as a string. Actions, passed as an object but not created yet, will be created. If the specified event is not assigned to the policy, it will be assigned. Args: event: Name of the event under which the actions will be assigned. actions: If :py:class:`list` (or similar), all of these actions will be set under TRUE section. If :py:class:`dict`, the action is key and value specifies its placement. If it's True, then it will be put in the TRUE section and so on. """ true_actions, false_actions = [], [] if isinstance(actions, Action): true_actions.append(actions) elif isinstance(actions, list) or isinstance( actions, tuple) or isinstance(actions, set): true_actions.extend(actions) elif isinstance(actions, dict): for action, is_true in actions.iteritems(): if is_true: true_actions.append(action) else: false_actions.append(action) else: raise TypeError( "assign_actions_to_event expects, list, tuple, set or dict!") # Check whether actions exist for action in true_actions + false_actions: if isinstance(action, Action): if not action.exists: action.create() assert action.exists, "Could not create action {}!".format( action.description) else: # string if not Action(action, "Tag").exists: raise NameError( "Action with name {} does not exist!".format(action)) # Check whether we have all necessary events assigned if not self.is_event_assigned(event): self.assign_events(event, extend=True) assert self.is_event_assigned( event), "Could not assign event {}!".format(event) # And now we can assign actions self.testing_event = event view = navigate_to(self, "Event Details") assert view.is_displayed view.configuration.item_select("Edit Actions for this Policy Event") view = self.create_view(EditEventView) assert view.is_displayed changed = view.fill({ "true_actions": [str(action) for action in true_actions], "false_actions": [str(action) for action in false_actions] }) if changed: view.save_button.click() else: view.cancel_button.click() view.flash.assert_no_error() view.flash.assert_message( 'Actions for Policy Event "{}" were saved'.format(event)) @property def exists(self): policies = self.appliance.db.client["miq_policies"] return self.appliance.db.client.session\ .query(policies.description)\ .filter(policies.description == self.description)\ .count() > 0 @property def assigned_events(self): policies = self.appliance.db.client["miq_policies"] events = self.appliance.db.client["miq_event_definitions"] policy_contents = self.appliance.db.client["miq_policy_contents"] session = self.appliance.db.client.session policy_id = session.query( policies.id).filter(policies.description == self.description) assigned_events = session.query( policy_contents.miq_event_definition_id).filter( policy_contents.miq_policy_id == policy_id) return [ event_name[0] for event_name in session.query(events.description).filter( events.id.in_(assigned_events)) ]
class BaseAlertProfile(Updateable, Navigatable, Pretty): TYPE = None _param_name = ParamClassName('description') pretty_attrs = ["description", "alerts"] def __init__(self, description, alerts=None, notes=None, appliance=None): Navigatable.__init__(self, appliance=appliance) self.description = description self.notes = notes self.alerts = alerts def create(self): view = navigate_to(self, "Add") view.fill({ "description": self.description, "notes": self.notes, "alerts": [str(alert) for alert in self.alerts] }) view.add_button.click() view = self.create_view(AlertProfileDetailsView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message('Alert Profile "{}" was added'.format( self.description)) def update(self, updates): """Update this Alert Profile in UI. Args: updates: Provided by update() context manager. cancel: Whether to cancel the update (default False). """ view = navigate_to(self, "Edit") changed = view.fill(updates) if changed: view.save_button.click() else: view.cancel_button.click() for attr, value in updates.items(): setattr(self, attr, value) view = self.create_view(AlertProfileDetailsView) assert view.is_displayed view.flash.assert_no_error() if changed: view.flash.assert_message('Alert Profile "{}" was saved'.format( updates.get("description", self.description))) else: view.flash.assert_message( 'Edit of Alert Profile "{}" was cancelled by the user'.format( self.description)) def delete(self, cancel=False): """Delete this Alert Profile in UI. Args: cancel: Whether to cancel the deletion (default False). """ view = navigate_to(self, "Details") view.configuration.item_select("Delete this Alert Profile", handle_alert=not cancel) if cancel: assert view.is_displayed view.flash.assert_no_error() else: view = self.create_view(AlertProfilesAllView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message( 'Alert Profile "{}": Delete successful'.format( self.description)) @property def exists(self): """Check existence of this Alert Profile. Returns: :py:class:`bool` signalizing the presence of the Alert Profile in database. """ miq_sets = self.appliance.db.client["miq_sets"] return self.appliance.db.client.session\ .query(miq_sets.description)\ .filter( miq_sets.description == self.description and miq_sets.set_type == "MiqAlertSet")\ .count() > 0 def assign_to(self, assign, selections=None, tag_category=None): """Assigns this Alert Profile to specified objects. Args: assign: Where to assign (The Enterprise, ...). selections: What items to check in the tree. N/A for The Enteprise. tag_category: Only for choices starting with Tagged. N/A for The Enterprise. """ view = navigate_to(self, "Edit assignments") changed = view.fill({ "assign_to": assign, "tag_category": tag_category, "selections": selections }) if changed: view.save_button.click() else: view.cancel_button.click() view = self.create_view(AlertProfileDetailsView) assert view.is_displayed view.flash.assert_no_error() if changed: view.flash.assert_message( 'Alert Profile "{}" assignments {} saved'.format( self.description, version.pick({ version.LOWEST: "succesfully", "5.8": "successfully", }))) else: view.flash.assert_message( 'Edit of Alert Profile "{}" was cancelled by the user'.format( self.description))