class VM(BaseVM): TO_RETIRE = None retire_form = Form(fields=[ ('date_retire', { version.LOWEST: date_retire_element, "5.5": AngularCalendarInput( "retirement_date", "//label[contains(normalize-space(.), 'Retirement Date')]")}), ('warn', sel.Select("select#retirement_warn")) ]) def retire(self): self.load_details(refresh=True) lcl_btn(self.TO_RETIRE, invokes_alert=True) sel.handle_alert() flash.assert_success_message({ version.LOWEST: "Retire initiated for 1 VM and Instance from the CFME Database", "5.5": "Retirement initiated for 1 VM and Instance from the CFME Database"}) def power_control_from_provider(self): raise NotImplementedError("You have to implement power_control_from_provider!") def power_control_from_cfme(self, option, cancel=True, from_details=False): """Power controls a VM from within CFME Args: option: corresponds to option values under the power button cancel: Whether or not to cancel the power operation on confirmation from_details: Whether or not to perform action from instance details page Raises: OptionNotAvailable: option param is not visible or enabled """ if (self.is_pwr_option_available_in_cfme(option=option, from_details=from_details)): pwr_btn(option, invokes_alert=True) sel.handle_alert(cancel=cancel, check_present=True) logger.info( "Power control action of VM/instance %s, option %s, cancel %s executed", self.name, option, str(cancel)) else: raise OptionNotAvailable(option + " is not visible or enabled") def wait_candu_data_available(self, timeout=600): """Waits until C&U data are available for this VM/Instance Args: timeout: Timeout passed to :py:func:`utils.wait.wait_for` """ self.load_details(refresh=True) wait_for( lambda: not toolbar.is_greyed('Monitoring', 'Utilization'), delay=10, handle_exception=True, num_sec=timeout, fail_func=lambda: toolbar.select("Reload")) def wait_for_vm_state_change(self, desired_state=None, timeout=300, from_details=False, with_relationship_refresh=True): """Wait for M to come to desired state. This function waits just the needed amount of time thanks to wait_for. Args: desired_state: on, off, suspended... for available states, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` timeout: Specify amount of time (in seconds) to wait Raises: TimedOutError: When instance does not come up to desired state in specified period of time. InstanceNotFound: When unable to find the instance passed """ detail_t = ("Power Management", "Power State") def _looking_for_state_change(): if from_details: self.load_details(refresh=True) return self.get_detail(properties=detail_t) == desired_state else: return 'currentstate-' + desired_state in self.find_quadicon().state return wait_for( _looking_for_state_change, num_sec=timeout, delay=30, fail_func=self.refresh_relationships if with_relationship_refresh else None) def is_pwr_option_available_in_cfme(self, option, from_details=False): """Checks to see if a power option is available on the VM Args: option: corresponds to option values under the power button, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` from_details: Whether or not to perform action from instance details page """ if from_details: self.load_details(refresh=True) else: self.find_quadicon(mark=True) try: return not toolbar.is_greyed('Power', option) except sel.NoSuchElementException: return False def delete_from_provider(self): if self.provider.mgmt.does_vm_exist(self.name): try: if self.provider.mgmt.is_vm_suspended(self.name): logger.debug("Powering up VM %s to shut it down correctly on %s.", self.name, self.provider.key) self.provider.mgmt.start_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) self.provider.mgmt.stop_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) except exceptions.ActionNotSupported: # Action is not supported on mgmt system. Simply continue pass # One more check (for the suspended one) if self.provider.mgmt.does_vm_exist(self.name): try: return self.provider.mgmt.delete_vm(self.name) except exceptions.VMInstanceNotFound: # Does not exist already return True else: return True def create_on_provider(self, timeout=900, find_in_cfme=False, **kwargs): """Create the VM on the provider Args: timeout: Number of seconds to wait for the VM to appear in CFME Will not wait at all, if set to 0 (Defaults to ``900``) """ deploy_template(self.provider.key, self.name, self.template_name, **kwargs) if find_in_cfme: self.provider.refresh_provider_relationships() self.wait_to_appear(timeout=timeout, load_details=False) def does_vm_exist_on_provider(self): """Check if VM exists on provider itself""" return self.provider.mgmt.does_vm_exist(self.name) def set_retirement_date(self, when, warn=None): """Sets the retirement date for this Vm object. It incorporates some magic to make it work reliably since the retirement form is not very pretty and it can't be just "done". Args: when: When to retire. :py:class:`str` in format mm/dd/yy of :py:class:`datetime.datetime` or :py:class:`utils.timeutil.parsetime`. warn: When to warn, fills the select in the form in case the ``when`` is specified. """ self.load_details() lcl_btn("Set Retirement Date") if callable(self.retire_form.date_retire): # It is the old functiton sel.wait_for_element("#miq_date_1") else: sel.wait_for_element(self.retire_form.date_retire) if when is None: try: wait_for(lambda: sel.is_displayed(retire_remove_button), num_sec=5, delay=0.2) sel.click(retire_remove_button) wait_for(lambda: not sel.is_displayed(retire_remove_button), num_sec=10, delay=0.2) sel.click(form_buttons.save) except TimedOutError: pass else: if sel.is_displayed(retire_remove_button): sel.click(retire_remove_button) wait_for(lambda: not sel.is_displayed(retire_remove_button), num_sec=15, delay=0.2) fill(self.retire_form.date_retire, when) wait_for(lambda: sel.is_displayed(retire_remove_button), num_sec=15, delay=0.2) if warn is not None: fill(self.retire_form.warn, warn) sel.click(form_buttons.save)
class VM(BaseVM): TO_RETIRE = None retire_form = Form(fields=[( 'date_retire', AngularCalendarInput( "retirement_date", "//label[contains(normalize-space(.), 'Retirement Date')]") ), ('warn', AngularSelect('retirementWarning'))]) def retire(self): self.load_details(refresh=True) lcl_btn(self.TO_RETIRE, invokes_alert=True) sel.handle_alert() flash.assert_success_message( 'Retirement initiated for 1 VM and Instance from the {} Database'. format( version.pick({ version.LOWEST: 'CFME', 'upstream': 'ManageIQ' }))) def power_control_from_provider(self): raise NotImplementedError( "You have to implement power_control_from_provider!") def power_control_from_cfme(self, option, cancel=True, from_details=False): """Power controls a VM from within CFME Args: option: corresponds to option values under the power button cancel: Whether or not to cancel the power operation on confirmation from_details: Whether or not to perform action from instance details page Raises: OptionNotAvailable: option param is not visible or enabled """ if (self.is_pwr_option_available_in_cfme(option=option, from_details=from_details)): pwr_btn(option, invokes_alert=True) sel.handle_alert(cancel=cancel, check_present=True) logger.info( "Power control action of VM/instance %s, option %s, cancel %s executed", self.name, option, str(cancel)) else: raise OptionNotAvailable(option + " is not visible or enabled") def wait_candu_data_available(self, timeout=600): """Waits until C&U data are available for this VM/Instance Args: timeout: Timeout passed to :py:func:`utils.wait.wait_for` """ self.load_details(refresh=True) wait_for(lambda: not toolbar.is_greyed('Monitoring', 'Utilization'), delay=10, handle_exception=True, num_sec=timeout, fail_func=lambda: toolbar.refresh()) def wait_for_vm_state_change(self, desired_state=None, timeout=300, from_details=False, with_relationship_refresh=True, from_any_provider=False): """Wait for M to come to desired state. This function waits just the needed amount of time thanks to wait_for. Args: desired_state: on, off, suspended... for available states, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` timeout: Specify amount of time (in seconds) to wait from_any_provider: Archived/Orphaned vms need this Raises: TimedOutError: When instance does not come up to desired state in specified period of time. InstanceNotFound: When unable to find the instance passed """ detail_t = ("Power Management", "Power State") def _looking_for_state_change(): if from_details: self.load_details(refresh=True) return self.get_detail(properties=detail_t) == desired_state else: return 'currentstate-' + desired_state in self.find_quadicon( from_any_provider=from_any_provider).data['state'] return wait_for( _looking_for_state_change, num_sec=timeout, delay=30, fail_func=lambda: self.refresh_relationships( from_details=from_details, from_any_provider=from_any_provider) if with_relationship_refresh else None) def is_pwr_option_available_in_cfme(self, option, from_details=False): """Checks to see if a power option is available on the VM Args: option: corresponds to option values under the power button, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` from_details: Whether or not to perform action from instance details page """ if from_details: self.load_details(refresh=True) else: entity = self.find_quadicon() entity.check() try: return not toolbar.is_greyed('Power', option) except sel.NoSuchElementException: return False def delete_from_provider(self): logger.info("Begin delete_from_provider") if self.provider.mgmt.does_vm_exist(self.name): try: if self.provider.mgmt.is_vm_suspended( self.name) and self.provider.type != 'azure': logger.debug( "Powering up VM %s to shut it down correctly on %s.", self.name, self.provider.key) self.provider.mgmt.start_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) self.provider.mgmt.stop_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) except exceptions.ActionNotSupported: # Action is not supported on mgmt system. Simply continue pass # One more check (for the suspended one) if self.provider.mgmt.does_vm_exist(self.name): try: logger.info("Mgmt System delete_vm") return self.provider.mgmt.delete_vm(self.name) except exceptions.VMInstanceNotFound: # Does not exist already return True else: return True def create_on_provider(self, timeout=900, find_in_cfme=False, **kwargs): """Create the VM on the provider Args: timeout: Number of seconds to wait for the VM to appear in CFME Will not wait at all, if set to 0 (Defaults to ``900``) """ deploy_template(self.provider.key, self.name, self.template_name, **kwargs) if find_in_cfme: self.provider.refresh_provider_relationships() self.wait_to_appear(timeout=timeout, load_details=False) def does_vm_exist_on_provider(self): """Check if VM exists on provider itself""" return self.provider.mgmt.does_vm_exist(self.name) def set_retirement_date(self, when, warn=None): """Sets the retirement date for this Vm object. It incorporates some magic to make it work reliably since the retirement form is not very pretty and it can't be just "done". Args: when: When to retire. :py:class:`str` in format mm/dd/yyyy of :py:class:`datetime.datetime` or :py:class:`utils.timeutil.parsetime`. warn: When to warn, fills the select in the form in case the ``when`` is specified. """ # TODO: refactor for retirement nav destinations and widget form fill when child classes self.load_details() lcl_btn("Set Retirement Date") if callable(self.retire_form.date_retire): # It is the old functiton sel.wait_for_element("#miq_date_1") else: sel.wait_for_element(self.retire_form.date_retire) if when is None: try: wait_for(lambda: sel.is_displayed(retire_remove_button), num_sec=5, delay=0.2) sel.click(retire_remove_button) wait_for(lambda: not sel.is_displayed(retire_remove_button), num_sec=10, delay=0.2) sel.click(form_buttons.save) except TimedOutError: pass else: if sel.is_displayed(retire_remove_button): sel.click(retire_remove_button) wait_for(lambda: not sel.is_displayed(retire_remove_button), num_sec=15, delay=0.2) fill(self.retire_form.date_retire, when) wait_for(lambda: sel.is_displayed(retire_remove_button), num_sec=15, delay=0.2) if warn is not None: fill(self.retire_form.warn, warn) sel.click(form_buttons.save) def equal_drift_results(self, row_text, section, *indexes): """ Compares drift analysis results of a row specified by it's title text Args: row_text: Title text of the row to compare section: Accordion section where the change happened; this section will be activated indexes: Indexes of results to compare starting with 0 for first row (latest result). Compares all available drifts, if left empty (default). Note: There have to be at least 2 drift results available for this to work. Returns: ``True`` if equal, ``False`` otherwise. """ # mark by indexes or mark all self.load_details(refresh=True) sel.click(InfoBlock("Properties", "Drift History")) if indexes: drift_table.select_rows_by_indexes(*indexes) else: # We can't compare more than 10 drift results at once # so when selecting all, we have to limit it to the latest 10 if len(list(drift_table.rows())) > 10: drift_table.select_rows_by_indexes(*range(0, min(10, len))) else: drift_table.select_all() tb.select("Select up to 10 timestamps for Drift Analysis") # Make sure the section we need is active/open sec_apply_btn = "//div[@id='accordion']/a[contains(normalize-space(text()), 'Apply')]" # Deselect other sections for other_section in drift_section.child_items(): drift_section.check_node(other_section.text) drift_section.uncheck_node(other_section.text) # Activate the required section drift_section.check_node(section) sel.click(sec_apply_btn) if not tb.is_active("All attributes"): tb.select("All attributes") drift_grid = DriftGrid() if any( drift_grid.cell_indicates_change(row_text, i) for i in range(0, len(indexes))): return False return True
class VM(BaseVM): TO_RETIRE = None retire_form_click_away = "//label[contains(normalize-space(.), 'Retirement Date')]" retire_form = Form(fields=[('date_retire', { version.LOWEST: AngularCalendarInput("retirement_date", retire_form_click_away), '5.9': AngularCalendarInput("retirement_date_datepicker", retire_form_click_away) }), ('warn', AngularSelect('retirementWarning'))]) def retire(self): self.load_details(refresh=True) lcl_btn(self.TO_RETIRE, invokes_alert=True) sel.handle_alert() flash.assert_success_message( 'Retirement initiated for 1 VM and Instance from the {} Database'. format( version.pick({ version.LOWEST: 'CFME', 'upstream': 'ManageIQ' }))) def power_control_from_provider(self): raise NotImplementedError( "You have to implement power_control_from_provider!") def power_control_from_cfme(self, option, cancel=True, from_details=False): """Power controls a VM from within CFME Args: option: corresponds to option values under the power button cancel: Whether or not to cancel the power operation on confirmation from_details: Whether or not to perform action from instance details page Raises: OptionNotAvailable: option param is not visible or enabled """ if (self.is_pwr_option_available_in_cfme(option=option, from_details=from_details)): pwr_btn(option, invokes_alert=True) sel.handle_alert(cancel=cancel, check_present=True) logger.info( "Power control action of VM/instance %s, option %s, cancel %s executed", self.name, option, str(cancel)) else: raise OptionNotAvailable(option + " is not visible or enabled") def wait_candu_data_available(self, timeout=600): """Waits until C&U data are available for this VM/Instance Args: timeout: Timeout passed to :py:func:`utils.wait.wait_for` """ self.load_details(refresh=True) wait_for(lambda: not toolbar.is_greyed('Monitoring', 'Utilization'), delay=10, handle_exception=True, num_sec=timeout, fail_func=lambda: toolbar.refresh()) def wait_for_vm_state_change(self, desired_state=None, timeout=300, from_details=False, with_relationship_refresh=True, from_any_provider=False): """Wait for VM to come to desired state. This function waits just the needed amount of time thanks to wait_for. Args: desired_state: on, off, suspended... for available states, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` timeout: Specify amount of time (in seconds) to wait from_any_provider: Archived/Orphaned vms need this Raises: TimedOutError: When instance does not come up to desired state in specified period of time. InstanceNotFound: When unable to find the instance passed """ detail_t = ("Power Management", "Power State") def _looking_for_state_change(): if from_details: self.load_details(refresh=True) return self.get_detail(properties=detail_t) == desired_state else: return 'currentstate-' + desired_state in self.find_quadicon( from_any_provider=from_any_provider).data['state'] return wait_for( _looking_for_state_change, num_sec=timeout, delay=30, fail_func=lambda: self.refresh_relationships( from_details=from_details, from_any_provider=from_any_provider) if with_relationship_refresh else None) def is_pwr_option_available_in_cfme(self, option, from_details=False): """Checks to see if a power option is available on the VM Args: option: corresponds to option values under the power button, see :py:class:`EC2Instance` and :py:class:`OpenStackInstance` from_details: Whether or not to perform action from instance details page """ if from_details: self.load_details(refresh=True) else: entity = self.find_quadicon() entity.check() try: return not toolbar.is_greyed('Power', option) except sel.NoSuchElementException: return False def delete_from_provider(self): logger.info("Begin delete_from_provider") if self.provider.mgmt.does_vm_exist(self.name): try: if self.provider.mgmt.is_vm_suspended( self.name) and self.provider.type != 'azure': logger.debug( "Powering up VM %s to shut it down correctly on %s.", self.name, self.provider.key) self.provider.mgmt.start_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) self.provider.mgmt.stop_vm(self.name) self.provider.mgmt.wait_vm_steady(self.name) except exceptions.ActionNotSupported: # Action is not supported on mgmt system. Simply continue pass # One more check (for the suspended one) if self.provider.mgmt.does_vm_exist(self.name): try: logger.info("Mgmt System delete_vm") return self.provider.mgmt.delete_vm(self.name) except exceptions.VMInstanceNotFound: # Does not exist already return True else: return True def create_on_provider(self, timeout=900, find_in_cfme=False, **kwargs): """Create the VM on the provider Args: timeout: Number of seconds to wait for the VM to appear in CFME Will not wait at all, if set to 0 (Defaults to ``900``) """ deploy_template(self.provider.key, self.name, self.template_name, **kwargs) if find_in_cfme: self.provider.refresh_provider_relationships() self.wait_to_appear(timeout=timeout, load_details=False) def does_vm_exist_on_provider(self): """Check if VM exists on provider itself""" return self.provider.mgmt.does_vm_exist(self.name) def set_retirement_date(self, when=None, offset=None, warn=None): """Overriding common method to use widgetastic views/widgets properly Args: when: :py:class:`datetime.datetime` object, when to retire (date in future) offset: :py:class:`dict` with months, weeks, days, hours keys. other keys ignored warn: When to warn, fills the select in the form in case the ``when`` is specified. Note: this should be moved up to the common VM class when infra+cloud+common are all WT If when and offset are both None, this removes retirement date Examples: # To set a specific retirement date 2 days from today two_days_later = datetime.date.today() + datetime.timedelta(days=2) vm.set_retirement_date(when=two_days_later) # To set a retirement offset 2 weeks from now vm.set_retirement_date(offset={weeks=2}) Offset is dict to remove ambiguity between timedelta/datetime and months/weeks/days/hours timedelta supports creation with weeks, but not months timedelta supports days attr, but not weeks or months timedelta days attr will report a total summary, not the component that was passed to it For these reasons timedelta isn't appropriate for offset An enhancement to cfme.utils.timeutil extending timedelta would be great for making this a bit cleaner """ new_retire = self.appliance.version >= "5.9" view = navigate_to(self, 'SetRetirement') fill_date = None fill_offset = None # explicit is/not None use here because of empty strings and dicts if when is not None and offset is not None: raise ValueError( 'set_retirement_date takes when or offset, but not both') if not new_retire and offset is not None: raise ValueError( 'Offset retirement only available in CFME 59z+ or miq-gaprindashvili' ) if when is not None and not isinstance(when, (datetime, date)): raise ValueError('when argument must be a datetime object') # due to major differences between the forms and their interaction, I'm splitting this # method into two major blocks, one for each version. As a result some patterns will be # repeated in both blocks # This will allow for making changes to one version or the other without strange # interaction in the logic # format the date # needs 4 digit year for fill # displayed 2 digit year for flash message if new_retire: # 59z/G-release retirement if when is not None and offset is None: # Specific datetime retire, H+M are 00:00 by default if just date passed fill_date = when.strftime('%m/%d/%Y %H:%M') # 4 digit year msg_date = when.strftime( '%m/%d/%y %H:%M UTC') # two digit year and timestamp msg = 'Retirement date set to {}'.format(msg_date) elif when is None and offset is None: # clearing retirement date with empty string in textinput fill_date = '' msg = 'Retirement date removed' elif offset is not None: # retirement by offset fill_date = None fill_offset = { k: v for k, v in offset.items() if k in ['months', 'weeks', 'days', 'hours'] } # hack together an offset # timedelta can take weeks, but not months # copy and pop, only used to generate message, not used for form fill offset_copy = fill_offset.copy() if 'months' in offset_copy: new_weeks = offset_copy.get( 'weeks', 0) + int(offset_copy.pop('months', 0)) * 4 offset_copy.update({'weeks': new_weeks}) msg_date = datetime.utcnow() + timedelta(**offset_copy) msg = 'Retirement date set to {}'.format( msg_date.strftime('%m/%d/%y %H:%M UTC')) # TODO move into before_fill when no need to click away from datetime picker view.form.fill({ 'retirement_mode': 'Time Delay from Now' if fill_offset else 'Specific Date and Time' }) view.flush_widget_cache( ) # since retirement_date is conditional widget if fill_date is not None: # specific check because of empty string # two part fill, widget seems to block warn selection when open changed_date = view.form.fill( {'retirement_date': { 'datetime_select': fill_date }}) view.title.click() # close datetime widget changed_warn = view.form.fill({'retirement_warning': warn}) changed = changed_date or changed_warn elif fill_offset: changed = view.form.fill({ 'retirement_date': fill_offset, 'retirement_warning': warn }) else: # 58z/euwe retirement if when: fill_date = when.strftime('%m/%d/%Y') # 4 digit year msg_date = when.strftime( '%m/%d/%y 00:00 UTC') # two digit year and default 0 UTC msg = 'Retirement date set to {}'.format(msg_date) else: fill_date = None msg = 'Retirement date removed' if fill_date: changed = view.form.fill({ 'retirement_date': fill_date, 'retirement_warning': warn }) else: if view.form.remove_date.is_displayed: view.form.remove_date.click() changed = True else: # no date set, nothing to change logger.info( 'Retirement date not set, cannot clear, canceling form' ) changed = False # Form save and flash messages are the same between versions if changed: view.form.save.click() else: logger.info( 'No form changes for setting retirement, clicking cancel') view.form.cancel.click() msg = 'Set/remove retirement date was cancelled by the user' if self.DETAILS_VIEW_CLASS is not None: view = self.create_view(self.DETAILS_VIEW_CLASS) assert view.is_displayed view.flash.assert_success_message(msg) def equal_drift_results(self, row_text, section, *indexes): """ Compares drift analysis results of a row specified by it's title text Args: row_text: Title text of the row to compare section: Accordion section where the change happened; this section will be activated indexes: Indexes of results to compare starting with 0 for first row (latest result). Compares all available drifts, if left empty (default). Note: There have to be at least 2 drift results available for this to work. Returns: ``True`` if equal, ``False`` otherwise. """ # mark by indexes or mark all self.load_details(refresh=True) sel.click(InfoBlock("Properties", "Drift History")) if indexes: drift_table.select_rows_by_indexes(*indexes) else: # We can't compare more than 10 drift results at once # so when selecting all, we have to limit it to the latest 10 if len(list(drift_table.rows())) > 10: drift_table.select_rows_by_indexes(*range(0, min(10, len))) else: drift_table.select_all() tb.select("Select up to 10 timestamps for Drift Analysis") # Make sure the section we need is active/open sec_apply_btn = "//div[@id='accordion']/a[contains(normalize-space(text()), 'Apply')]" # Deselect other sections for other_section in drift_section.child_items(): drift_section.check_node(other_section.text) drift_section.uncheck_node(other_section.text) # Activate the required section drift_section.check_node(section) sel.click(sec_apply_btn) if not tb.is_active("All attributes"): tb.select("All attributes") drift_grid = DriftGrid() if any( drift_grid.cell_indicates_change(row_text, i) for i in range(0, len(indexes))): return False return True