Пример #1
0
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
Пример #2
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))
Пример #3
0
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")
Пример #4
0
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)
Пример #5
0
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))