Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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