Ejemplo n.º 1
0
class Server(BaseEntity, sentaku.modeling.ElementMixin):
    _param_name = ParamClassName('name')

    name = attr.ib()
    sid = attr.ib(default=1)

    address = sentaku.ContextualMethod()
    login = sentaku.ContextualMethod()
    login_admin = sentaku.ContextualMethod()
    logout = sentaku.ContextualMethod()
    update_password = sentaku.ContextualMethod()
    logged_in = sentaku.ContextualMethod()
    current_full_name = sentaku.ContextualMethod()
    current_username = sentaku.ContextualMethod()
    current_group_name = sentaku.ContextualMethod()

    # zone = sentaku.ContextualProperty()
    # slave_servers = sentaku.ContextualProperty()

    @property
    def settings(self):
        from cfme.configure.configuration.server_settings import ServerInformation
        setting = ServerInformation(appliance=self.appliance)
        return setting

    @property
    def authentication(self):
        from cfme.configure.configuration.server_settings import AuthenticationSetting
        auth_settings = AuthenticationSetting(self.appliance)
        return auth_settings

    @property
    def collect_logs(self):
        from cfme.configure.configuration.diagnostics_settings import ServerCollectLog
        return ServerCollectLog(self.appliance)

    @property
    def zone(self):
        server_res = self.appliance.rest_api.collections.servers.find_by(
            id=self.sid)
        server = server_res[0]
        server.reload(attributes=['zone'])
        zone = server.zone
        zone_obj = self.appliance.collections.zones.instantiate(
            name=zone['name'], description=zone['description'], id=zone['id'])
        return zone_obj

    @property
    def slave_servers(self):
        return self.zone.collections.servers.filter({'slave': True}).all()
Ejemplo n.º 2
0
class BaseCondition(BaseEntity, Updateable, Pretty):

    TREE_NODE = None
    PRETTY = None
    FIELD_VALUE = None
    _param_name = ParamClassName('description')

    description = attr.ib()
    expression = attr.ib(default=None)
    scope = attr.ib(default=None)
    notes = attr.ib(default=None)

    def update(self, updates):
        """Update this Condition in UI.

        Args:
            updates: Provided by update() context manager.
        """
        view = navigate_to(self, "Edit")
        view.fill(updates)
        view.save_button.click()
        view = self.create_view(ConditionDetailsView, override=updates, wait="10s")
        view.flash.assert_success_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(ConditionClassAllView, wait="20s")
            view.flash.assert_success_message('Condition "{}": Delete successful'.format(
                self.description))

    def read_expression(self):
        view = navigate_to(self, "Details")
        assert view.is_displayed
        return view.expression.text

    def read_scope(self):
        view = navigate_to(self, "Details")
        assert view.is_displayed
        return view.scope.text

    @property
    def exists(self):
        """Check existence of this Condition.

        Returns: :py:class:`bool` signalizing the presence of the Condition in the database.
        """
        try:
            self.appliance.rest_api.collections.conditions.get(description=self.description)
            return True
        except ValueError:
            return False
Ejemplo n.º 3
0
class BaseVM(
        BaseEntity,
        Pretty,
        Updateable,
        PolicyProfileAssignable,
        Taggable,
        ConsoleMixin,
        CustomButtonEventsMixin,
):
    """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']

    ###
    # 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 = 'Remove selected items from Inventory'
    REMOVE_SINGLE = 'Remove Virtual Machine from Inventory'
    RETIRE_DATE_FMT = parsetime.saved_report_title_format
    _param_name = ParamClassName('name')
    DETAILS_VIEW_CLASS = None

    ###
    # Shared behaviour
    #
    PROVISION_CANCEL = 'Add of new VM Provision Request was cancelled by the user'
    PROVISION_START = (
        'VM Provision Request was Submitted, you will be notified when your VMs '
        'are ready')
    name = attr.ib()
    provider = attr.ib()

    def __new__(cls, *args, **kwargs):
        if cls in [BaseVM, VM, Template]:
            raise NotImplementedError('This class cannot be instantiated.')
        else:
            # magic {waves hands}
            return object.__new__(cls)

    ###
    # Properties
    #
    @property
    def is_vm(self):
        return not isinstance(self, _TemplateMixin)

    @property
    def quadicon_type(self):
        return self.QUADICON_TYPE

    ###
    # Methods
    #
    def check_compliance(self, timeout=240):
        """Initiates compliance check and waits for it to finish."""
        view = navigate_to(self, "Details")
        original_state = self.compliance_status
        view.toolbar.policy.item_select(
            "Check Compliance of Last Known Configuration", handle_alert=True)
        view.flash.assert_no_error()
        wait_for(lambda: self.compliance_status != original_state,
                 num_sec=timeout,
                 delay=5,
                 message="compliance of {} checked".format(self.name))

    @property
    def compliance_status(self):
        """Returns the title of the compliance SummaryTable. The title contains datetime so it can
        be compared.

        Returns:
            :py:class:`NoneType` if no title is present (no compliance checks before), otherwise str
        """
        view = navigate_to(self, "Details")
        view.toolbar.reload.click()
        return view.entities.summary("Compliance").get_text_of("Status")

    @property
    def compliant(self):
        """Check if the VM is compliant.

        Returns:
            :py:class:`bool`
        """
        text = self.compliance_status.strip().lower()
        if text.startswith("non-compliant"):
            return False
        elif text.startswith("compliant"):
            return True
        else:
            raise ValueError(
                "{} is not a known state for compliance".format(text))

    def 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:
            view = navigate_to(self, 'Details')
            view.toolbar.configuration.item_select(self.REMOVE_SINGLE,
                                                   handle_alert=not cancel)
        else:
            view = navigate_to(self.parent, 'All')
            self.find_quadicon().check()
            view.toolbar.configuration.item_select(self.REMOVE_SELECTED,
                                                   handle_alert=not cancel)

    @property
    def ip_address(self):
        """Fetches IP Address of VM"""
        return self.mgmt.ip

    @property
    def mac_address(self):
        """Fetches MAC Address of VM"""
        # TODO: We should update this with wrapanapi method when it becomes available.
        view = navigate_to(self, "Details", use_resetter=False)
        try:
            return view.entities.summary('Properties').get_text_of(
                "MAC Address")
        except NameError:
            # since some providers have plural 'Addresses'.
            return view.entities.summary('Properties').get_text_of(
                "MAC Addresses")

    @property
    def is_retired(self):
        """Check retirement status of vm"""
        view = navigate_to(self, "Details", use_resetter=False)
        if view.entities.summary('Lifecycle').get_text_of(
                'Retirement Date').lower() != 'never':
            try:
                retirement_state = VersionPicker({
                    LOWEST: 'Retirement state',
                    '5.10': 'Retirement State'
                })
                status = view.entities.summary('Lifecycle').get_text_of(
                    retirement_state).lower()
                return status == 'retired'
            except NameError:
                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.parent, 'All')
        else:
            view = navigate_to(self, 'AllForProvider', use_resetter=False)

        view.toolbar.view_selector.select('Grid View')
        try:
            return view.entities.get_entity(name=self.name,
                                            surf_pages=True,
                                            use_search=use_search)
        except ItemNotFound:
            raise VmOrInstanceNotFound("VM '{}' not found in UI!".format(
                self.name))

    def open_console(self, console='VM Console', invokes_alert=None):
        """
        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.
        """
        # 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=invokes_alert)
        self.vm_console

    def open_details(self, properties=None):
        """Clicks on details infoblock"""
        view = navigate_to(self, 'Details')
        view.entities.summary(properties[0]).click_at(properties[1])
        return self.create_view(VMPropertyDetailView)

    @property
    def last_analysed(self):
        """Returns the contents of the ``Last Analysed`` field in summary"""
        view = navigate_to(self, "Details")
        view.toolbar.reload.click()
        return view.entities.summary("Lifecycle").get_text_of(
            "Last Analyzed").strip()

    def load_details(self, refresh=False, from_any_provider=False):
        """Navigates to an VM's details page.

        Args:
            refresh: Refreshes the VM page if already there
            from_any_provider: Archived/Orphaned VMs need this
        """
        if from_any_provider:
            view = navigate_to(self, 'AnyProviderDetails', use_resetter=False)
        else:
            view = navigate_to(self, 'Details', use_resetter=False)
        if refresh:
            view.toolbar.reload.click()

        view.wait_displayed()
        return view

    def open_edit(self):
        """Loads up the edit page of the object."""
        return navigate_to(self, '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:
            view = navigate_to(self, 'Details', use_resetter=False)
        else:
            view = navigate_to(self.parent, 'All')
            self.find_quadicon(from_any_provider=from_any_provider).check()
        view.toolbar.configuration.item_select(
            "Refresh Relationships and Power States", handle_alert=not cancel)

    @property
    def retirement_date(self):
        """Returns the retirement date of the selected machine, or 'Never'

        Returns:
            :py:class:`str` object
        """
        view = navigate_to(self, "Details")
        return view.entities.summary("Lifecycle").get_text_of(
            "Retirement Date").strip()

    def smartstate_scan(self,
                        cancel=False,
                        from_details=False,
                        wait_for_task_result=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:
            view = navigate_to(self, 'Details', use_resetter=False)
        else:
            view = navigate_to(self.parent, 'All')
            self.find_quadicon().check()
        view.toolbar.configuration.item_select('Perform SmartState Analysis',
                                               handle_alert=not cancel)
        if wait_for_task_result:
            task = self.appliance.collections.tasks.instantiate(
                name='Scan from Vm {}'.format(self.name), tab='AllTasks')
            task.wait_for_finished()
            return task

    def wait_to_disappear(self, timeout=600):
        """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=5,
                 fail_func=self.browser.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
        """
        def _refresh():
            self.provider.refresh_provider_relationships()
            self.appliance.browser.widgetastic.browser.refresh(
            )  # strange because ViaUI

        wait_for(lambda: self.exists,
                 num_sec=timeout,
                 delay=5,
                 fail_func=_refresh,
                 message="wait for vm to appear")
        if load_details:
            navigate_to(self, "Details", use_resetter=False)

    def set_ownership(self,
                      user=None,
                      group=None,
                      click_cancel=False,
                      click_reset=False):
        """Set instance ownership

        Args:
            user (User): user object for ownership
            group (Group): group object for ownership
            click_cancel (bool): Whether to cancel form submission
            click_reset (bool): Whether to reset form after filling
        """
        view = navigate_to(self, 'SetOwnership', wait_for_view=0)
        fill_result = view.form.fill({
            'user_name':
            user.name if user else None,
            'group_name':
            group.description if group else group
        })
        if not fill_result:
            view.form.cancel_button.click()
            view = self.create_view(navigator.get_class(self, 'Details').VIEW)
            view.flash.assert_success_message(
                'Set Ownership was cancelled by the user')
            return

        # Only if the form changed
        if click_reset:
            view.form.reset_button.click()
            view.flash.assert_message('All changes have been reset', 'warning')
            # Cancel after reset
            assert view.form.is_displayed
            view.form.cancel_button.click()
        elif click_cancel:
            view.form.cancel_button.click()
            view.flash.assert_success_message(
                'Set Ownership was cancelled by the user')
        else:
            # save the form
            view.form.save_button.click()
            view = self.create_view(navigator.get_class(self, 'Details').VIEW)
            view.flash.assert_success_message(
                'Ownership saved for selected {}'.format(self.VM_TYPE))

    def unset_ownership(self):
        """Remove user ownership and return group to EvmGroup-Administrator"""
        view = navigate_to(self, 'SetOwnership', wait_for_view=0)
        fill_result = view.form.fill({
            'user_name': '<No Owner>',
            'group_name': 'EvmGroup-administrator'
        })
        if fill_result:
            view.form.save_button.click()
            msg = 'Ownership saved for selected {}'.format(self.VM_TYPE)
        else:
            view.form.cancel_button.click()
            logger.warning('No change during unset_ownership')
            msg = 'Set Ownership was cancelled by the user'

        view = self.create_view(navigator.get_class(self, 'Details').VIEW)
        view.flash.assert_success_message(msg)

    def rename(self, new_vm_name, cancel=False, reset=False):
        """Rename the VM

        Args:
            new_vm_name: object for renaming vm
            cancel (bool): Whether to cancel form submission
            reset (bool): Whether to reset form after filling
        """
        view = navigate_to(self, 'Rename')
        changed = view.vm_name.fill(new_vm_name)
        if changed:
            if reset:
                view.reset_button.click()
                view.flash.assert_no_error()
                view.cancel_button.click()
            else:
                # save the form
                view.save_button.click()
                view.flash.assert_no_error()
                self.name = new_vm_name
                return self
        if cancel:
            view.cancel_button.click()
            view.flash.assert_no_error()
Ejemplo n.º 4
0
class BaseProvider(WidgetasticTaggable, Updateable, SummaryMixin, Navigatable):
    # List of constants that every non-abstract subclass must have defined
    _param_name = ParamClassName('name')
    STATS_TO_MATCH = []
    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 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 id(self):
        """"
        Return the ID associated with the specified provider name
        """
        return self.appliance.rest_api.collections.providers.find_by(
            name=self.name)[0].id

    @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 cfme.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 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 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)
                    # sometimes we have cases that we need to validate even though
                    # there is no credentials, such as Hawkular endpoint
                    if (validate_credentials
                            and hasattr(endp_view, 'validate')
                            and endp_view.validate.is_displayed):
                        # there are some endpoints which don't demand validation like
                        #  RSA key pair
                        endp_view.validate.click()
                        # Flash message widget is in add_view, not in endpoints tab
                        logger.info(
                            'Validating credentials flash message for endpoint %s',
                            endpoint_name)
                        add_view.flash.assert_no_error()
                        add_view.flash.assert_success_message(
                            'Credential validation was successful')

            main_view = self.create_view(navigator.get_class(self, 'All').VIEW)
            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

    def create_rest(self):

        logger.info('Setting up provider: %s via rest', self.key)
        try:
            self.appliance.rest_api.collections.providers.action.create(
                hostname=self.hostname,
                ipaddress=self.ip_address,
                name=self.name,
                type="ManageIQ::Providers::{}".format(self.db_types[0]),
                credentials={
                    'userid': self.endpoints['default'].credentials.principal,
                    'password': self.endpoints['default'].credentials.secret
                })

            return self.appliance.rest_api.response.status_code == 200
        except APIException:
            return None

    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
        """
        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)
                # sometimes we have cases that we need to validate even though
                # there is no credentials, such as Hawkular endpoint
                if (validate_credentials and hasattr(endp_view, 'validate')
                        and endp_view.validate.is_displayed):
                    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)
                if hasattr(endp_view,
                           'validate') and endp_view.validate.is_displayed:
                    endp_view.validate.click()

        details_view = self.create_view(
            navigator.get_class(self, 'Details').VIEW)
        main_view = self.create_view(navigator.get_class(self, 'All').VIEW)

        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)

            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")

    def delete(self, cancel=True):
        """
        Deletes a provider from CFME

        Args:
            cancel: Whether to cancel the deletion, defaults to True
        """
        view = navigate_to(self, 'Details')
        item_title = version.pick({
            '5.9': 'Remove this {} Provider from Inventory',
            version.LOWEST: 'Remove this {} Provider'
        })
        view.toolbar.configuration.item_select(item_title.format(
            self.string_name),
                                               handle_alert=not cancel)
        if not cancel:
            msg = ('Delete initiated for 1 {} Provider from '
                   'the {} Database'.format(self.string_name,
                                            self.appliance.product_name))
            view.flash.assert_success_message(msg)

    def setup(self, rest=False):
        """
        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, refresh_delta=600):
        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, refresh_delta):
            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:
            view = navigate_to(self, 'All')
            entity = view.entities.get_entity(name=self.name, surf_pages=True)
            entity.check()

        else:
            view = navigate_to(self, 'Details')

        view.toolbar.configuration.item_select(self.refresh_text,
                                               handle_alert=True)

    @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:
            self.browser.selenium.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):
        view = navigate_to(self, 'All')

        def is_entity_present():
            try:
                view.entities.get_entity(name=self.name, surf_pages=True)
                return True
            except ItemNotFound:
                return False

        logger.info('Waiting for a provider to delete...')
        wait_for(is_entity_present,
                 fail_condition=True,
                 message="Wait provider to disappear",
                 num_sec=1000,
                 fail_func=self.browser.selenium.refresh)

    def load_details(self, refresh=False):
        """To be compatible with the Taggable and PolicyProfileAssignable mixins.

        Returns: ProviderDetails view
        """
        view = navigate_to(self, 'Details')
        if refresh:
            view.toolbar.reload.click()
        return view

    def get_detail(self, *ident):
        """ Gets details from the details infoblock

        The function first ensures that we are on the detail page for the specific provider.

        Args:
            *ident: An SummaryTable title, followed by the Key name, e.g. "Relationships", "Images"


        Returns: A string representing the contents of passed field value.
        """
        view = self.load_details()
        block, field = ident
        return getattr(view.entities, block.lower()).get_text_of(field)

    @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 cfme.utils.appliance import current_appliance as app
        app.rest_api.collections.providers.reload()
        # cfme 5.9 doesn't allow to remove provider thru api
        bz_blocked = BZ(1501941, forced_streams=['5.9']).blocks
        if app.version < '5.9' or (app.version >= '5.9' and not bz_blocked):
            for prov in app.rest_api.collections.providers.all:
                try:
                    if any(db_type in prov.type for db_type in cls.db_types):
                        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
        else:
            # Delete all matching
            for prov in app.managed_known_providers:
                if prov.one_of(cls):
                    logger.info('Deleting provider: %s', prov.name)
                    prov.delete(cancel=False)
            # Wait for all matching to be deleted
            for prov in app.managed_known_providers:
                if prov.one_of(cls):
                    prov.wait_for_delete()
        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")

    # These methods need to be overridden in the provider specific classes
    def get_console_connection_status(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_remote_console_canvas(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_console_ctrl_alt_del_btn(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_console_fullscreen_btn(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_all_provider_ids(self):
        """
        Returns an integer list of provider ID's via the REST API
        """
        # TODO: Move to ProviderCollection
        logger.debug('Retrieving the list of provider ids')

        provider_ids = []
        try:
            for prov in self.appliance.rest_api.collections.providers.all:
                provider_ids.append(prov.id)
        except APIException:
            return None

        return provider_ids

    def get_all_vm_ids(self):
        """
        Returns an integer list of vm ID's via the REST API
        """
        # TODO: Move to VMCollection or BaseVMCollection
        logger.debug('Retrieving the list of vm ids')

        vm_ids = []
        try:
            for vm in self.appliance.rest_api.collections.vms.all:
                vm_ids.append(vm.id)
        except APIException:
            return None

        return vm_ids

    def get_all_host_ids(self):
        """
        Returns an integer list of host ID's via the Rest API
        """
        # TODO: Move to HostCollection
        logger.debug('Retrieving the list of host ids')

        host_ids = []
        try:
            for host in self.appliance.rest_api.collections.hosts.all:
                host_ids.append(host.id)
        except APIException:
            return None
        return host_ids

    def get_all_template_ids(self):
        """Returns an integer list of template ID's via the Rest API"""
        # TODO: Move to TemplateCollection
        logger.debug('Retrieving the list of template ids')

        template_ids = []
        try:
            for template in self.appliance.rest_api.collections.templates.all:
                template_ids.append(template.id)
        except APIException:
            return None
        return template_ids

    def get_provider_details(self, provider_id):
        """Returns the name, and type associated with the provider_id"""
        # TODO: Move to ProviderCollection.find
        logger.debug(
            'Retrieving the provider details for ID: {}'.format(provider_id))

        details = {}
        try:
            prov = self.appliance.rest_api.collections.providers.get(
                id=provider_id)
        except APIException:
            return None
        details['id'] = prov.id
        details['name'] = prov.name
        details['type'] = prov.type

        return details

    def get_vm_details(self, vm_id):
        """
        Returns the name, type, vendor, host_id, and power_state associated with
        the vm_id.
        """
        # TODO: Move to VMCollection.find
        logger.debug('Retrieving the VM details for ID: {}'.format(vm_id))

        details = {}
        try:
            vm = self.appliance.rest_api.collections.vms.get(id=vm_id)
        except APIException:
            return None

        details['id'] = vm.id
        details['ems_id'] = vm.ems_id
        details['name'] = vm.name
        details['type'] = vm.type
        details['vendor'] = vm.vendore
        details['host_id'] = vm.host_id
        details['power_state'] = vm.power_state
        return details

    def get_template_details(self, template_id):
        """
        Returns the name, type, and guid associated with the template_id
        """
        # TODO: Move to TemplateCollection.find
        logger.debug(
            'Retrieving the template details for ID: {}'.format(template_id))

        template_details = {}
        try:
            template = self.appliance.rest_api.collections.templates.get(
                id=template_id)
        except APIException:
            return None

        template_details['name'] = template.name
        template_details['type'] = template.type
        template_details['guid'] = template.guid
        return template_details

    def get_all_template_details(self):
        """
        Returns a dictionary mapping template ids to their name, type, and guid
        """
        # TODO: Move to TemplateCollection.all
        all_details = {}
        for id in self.get_all_template_ids():
            all_details[id] = self.get_template_details(id)
        return all_details

    def get_vm_id(self, vm_name):
        """
        Return the ID associated with the specified VM name
        """
        # TODO: Get Provider object from VMCollection.find, then use VM.id to get the id
        logger.debug('Retrieving the ID for VM: {}'.format(vm_name))
        for vm_id in self.get_all_vm_ids():
            details = self.get_vm_details(vm_id)
            if details['name'] == vm_name:
                return vm_id

    def get_vm_ids(self, vm_names):
        """
        Returns a dictionary mapping each VM name to it's id
        """
        # TODO: Move to VMCollection.find or VMCollection.all
        name_list = vm_names[:]
        logger.debug('Retrieving the IDs for {} VM(s)'.format(len(name_list)))
        id_map = {}
        for vm_id in self.get_all_vm_ids():
            if not name_list:
                break
            vm_name = self.get_vm_details(vm_id)['name']
            if vm_name in name_list:
                id_map[vm_name] = vm_id
                name_list.remove(vm_name)
        return id_map

    def get_template_guids(self, template_dict):
        """
        Returns a list of tuples. The inner tuples are formated so that each guid
        is in index 0, and its provider's name is in index 1. Expects a dictionary
        mapping a provider to its templates
        """
        # TODO: Move to TemplateCollection
        result_list = []
        all_template_details = self.get_all_template_details()
        for provider, templates in template_dict.iteritems():
            for template_name in templates:
                inner_tuple = ()
                for id in all_template_details:
                    if ((all_template_details[id]['name'] == template_name)
                            and (self.db_types[0]
                                 in all_template_details[id]['type'])):
                        inner_tuple += (all_template_details[id]['guid'], )
                        inner_tuple += (provider, )
                        result_list.append(inner_tuple)
        return result_list
Ejemplo n.º 5
0
class PXEServer(Updateable, Pretty, Navigatable):
    """Model of a PXE Server object in CFME

    Args:
        name: Name of PXE server.
        depot_type: Depot type, either Samba or Network File System.
        uri: The Depot URI.
        userid: The Samba username.
        password: The Samba password.
        access_url: HTTP access path for PXE server.
        pxe_dir: The PXE dir for accessing configuration.
        windows_dir: Windows source directory.
        customize_dir: Customization directory for templates.
        menu_filename: Menu filename for iPXE/syslinux menu.
    """
    pretty_attrs = ['name', 'uri', 'access_url']
    _param_name = ParamClassName('name')

    def __init__(self, name=None, depot_type=None, uri=None, userid=None, password=None,
                 access_url=None, pxe_dir=None, windows_dir=None, customize_dir=None,
                 menu_filename=None, appliance=None):
        Navigatable.__init__(self, appliance=appliance)
        self.name = name
        self.depot_type = depot_type
        self.uri = uri
        self.userid = userid
        # todo: turn into Credentials class
        self.password = password
        self.access_url = access_url
        self.pxe_dir = pxe_dir
        self.windows_dir = windows_dir
        self.customize_dir = customize_dir
        self.menu_filename = menu_filename

    def create(self, cancel=False, refresh=True, refresh_timeout=120):
        """
        Creates a PXE server object

        Args:
            cancel (boolean): Whether to cancel out of the creation.  The cancel is done
                after all the information present in the PXE Server has been filled in the UI.
            refresh (boolean): Whether to run the refresh operation on the PXE server after
                the add has been completed.
        """
        view = navigate_to(self, 'Add')
        view.fill({'name': self.name,
                   'depot_type': self.depot_type,
                   'access_url': self.access_url,
                   'pxe_dir': self.pxe_dir,
                   'windows_images_dir': self.windows_dir,
                   'customization_dir': self.customize_dir,
                   'filename': self.menu_filename,
                   'uri': self.uri,
                   # Samba only
                   'username': self.userid,
                   'password': self.password,
                   'confirm_password': self.password})
        if self.depot_type == 'Samba' and self.userid and self.password:
            view.validate.click()

        main_view = self.create_view(PXEServersView)
        if cancel:
            view.cancel.click()
            main_view.flash.assert_success_message('Add of new PXE Server '
                                                   'was cancelled by the user')
        else:
            view.add.click()
            main_view.flash.assert_no_error()
            if refresh:
                self.refresh(timeout=refresh_timeout)

    @variable(alias="db")
    def exists(self):
        """
        Checks if the PXE server already exists
        """
        dbs = self.appliance.db.client
        candidates = list(dbs.session.query(dbs["pxe_servers"]))
        return self.name in [s.name for s in candidates]

    @exists.variant('ui')
    def exists_ui(self):
        """
        Checks if the PXE server already exists
        """
        try:
            navigate_to(self, 'Details')
            return True
        except NoSuchElementException:
            return False

    def update(self, updates, cancel=False):
        """
        Updates a PXE server 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.
        """

        view = navigate_to(self, 'Edit')
        view.fill(updates)
        if updates.get('userid') or updates.get('password'):
            view.validate.click()

        name = updates.get('name') or self.name
        main_view = self.create_view(PXEServersView, override=updates)
        if cancel:
            view.cancel.click()
            main_view.flash.assert_success_message('Edit of PXE Server "{}" was '
                                                   'cancelled by the user'.format(name))
        else:
            view.save.click()
            main_view.flash.assert_no_error()

    def delete(self, cancel=True):
        """
        Deletes a PXE server from CFME

        Args:
            cancel: Whether to cancel the deletion, defaults to True
        """
        view = navigate_to(self, 'Details')
        view.toolbar.configuration.item_select('Remove this PXE Server from Inventory',
            handle_alert=not cancel)
        if not cancel:
            main_view = self.create_view(PXEServersView)
            main_view.flash.assert_no_error()
        else:
            navigate_to(self, 'Details')

    def refresh(self, wait=True, timeout=120):
        """ Refreshes the PXE relationships and waits for it to be updated
        """
        view = navigate_to(self, 'Details')
        last_time = view.entities.basic_information.get_text_of('Last Refreshed On')
        view.toolbar.configuration.item_select('Refresh Relationships', handle_alert=True)
        view.flash.assert_success_message('PXE Server "{}": Refresh Relationships '
                                          'successfully initiated'.format(self.name))
        if wait:
            basic_info = view.entities.basic_information
            wait_for(lambda lt: lt != basic_info.get_text_of('Last Refreshed On'),
                     func_args=[last_time], fail_func=view.toolbar.reload.click, num_sec=timeout,
                     message="pxe refresh")

    @variable(alias='db')
    def get_pxe_image_type(self, image_name):
        pxe_i = self.appliance.db.client["pxe_images"]
        pxe_s = self.appliance.db.client["pxe_servers"]
        pxe_t = self.appliance.db.client["pxe_image_types"]
        hosts = list(self.appliance.db.client.session.query(pxe_t.name)
                     .join(pxe_i, pxe_i.pxe_image_type_id == pxe_t.id)
                     .join(pxe_s, pxe_i.pxe_server_id == pxe_s.id)
                     .filter(pxe_s.name == self.name)
                     .filter(pxe_i.name == image_name))
        if hosts:
            return hosts[0][0]
        else:
            return None

    @get_pxe_image_type.variant('ui')
    def get_pxe_image_type_ui(self, image_name):
        view = navigate_to(self, 'Details')
        view.sidebar.servers.tree.click_path('All PXE Servers', self.name,
                                             'PXE Images', image_name)
        details_view = self.create_view(PXESystemImageTypeDetailsView)
        return details_view.entities.basic_information.get_text_of('Type')

    def set_pxe_image_type(self, image_name, image_type):
        """
        Function to set the image type of a PXE image
        """
        # todo: maybe create appropriate navmazing destinations instead ?
        if self.get_pxe_image_type(image_name) != image_type:
            view = navigate_to(self, 'Details')
            view.sidebar.servers.tree.click_path('All PXE Servers', self.name,
                                                 'PXE Images', image_name)
            details_view = self.create_view(PXESystemImageTypeDetailsView)
            details_view.toolbar.configuration.item_select('Edit this PXE Image')
            edit_view = self.create_view(PXEImageEditView)
            edit_view.fill({'type': image_type})
            edit_view.save.click()
Ejemplo n.º 6
0
class Report(BaseEntity, Updateable):
    _param_name = ParamClassName('title')
    menu_name = attr.ib(default=None)
    title = attr.ib(default=None)
    company_name = attr.ib()
    type = attr.ib(default=None)
    subtype = attr.ib(default=None)
    base_report_on = attr.ib(default=None)
    report_fields = attr.ib(default=None)
    cancel_after = attr.ib(default=None)
    consolidation = attr.ib(default=None)
    formatting = attr.ib(default=None)
    styling = attr.ib(default=None)
    filter = attr.ib(default=None)
    filter_show_costs = attr.ib(default=None)
    filter_owner = attr.ib(default=None)
    filter_tag_cat = attr.ib(default=None)
    filter_tag_value = attr.ib(default=None)
    interval = attr.ib(default=None)
    interval_size = attr.ib(default=None)
    interval_end = attr.ib(default=None)
    sort = attr.ib(default=None)
    chart_type = attr.ib(default=None)
    top_values = attr.ib(default=None)
    sum_other = attr.ib(default=None)
    base_timeline_on = attr.ib(default=None)
    band_units = attr.ib(default=None)
    event_position = attr.ib(default=None)
    show_event_unit = attr.ib(default=None)
    show_event_count = attr.ib(default=None)
    summary = attr.ib(default=None)
    charts = attr.ib(default=None)
    timeline = attr.ib(default=None)
    is_candu = attr.ib(default=False)

    def __attrs_post_init__(self):
        self._collections = {'saved_reports': SavedReportsCollection}

    @company_name.default
    def company_name_default(self):
        return "My Company (All Groups)"

    def update(self, updates):
        view = navigate_to(self, "Edit")
        changed = view.fill(updates)
        if changed:
            view.save_button.click()
        else:
            view.cancel_button.click()
        view = self.create_view(ReportDetailsView,
                                override=updates,
                                wait='10s')
        view.flash.assert_no_error()
        if changed:
            view.flash.assert_message(f'Report "{self.menu_name}" was saved')
        else:
            view.flash.assert_message(
                f'Edit of Report "{self.menu_name}" was cancelled by the user')

    def copy(self):
        """ Copy a report via UI and return a copy of a Report object"""
        menu_name = f"Copy of {self.menu_name}"

        view = navigate_to(self, "Copy")
        view.add_button.click()
        self.create_view(AllReportsView, wait="5s")

        return self.appliance.collections.reports.instantiate(
            type=self.company_name,
            subtype="Custom",
            menu_name=menu_name,
            title=self.title,
        )

    def delete(self, cancel=False):
        view = navigate_to(self, "Details")
        node = view.reports.tree.expand_path("All Reports", self.company_name,
                                             "Custom")
        custom_reports_number = len(view.reports.tree.child_items(node))
        view.configuration.item_select("Delete this Report from the Database",
                                       handle_alert=not cancel)
        if cancel:
            view.wait_displayed()
            view.flash.assert_no_error()
        else:
            # This check is needed because after deleting the last custom report,
            # the whole "My Company (All EVM Groups)" branch in the tree will be removed.
            if custom_reports_number > 1:
                view = self.create_view(AllCustomReportsView, wait='5s')
            view.flash.assert_no_error()
            view.flash.assert_message(
                f'Report "{self.menu_name}": Delete successful')

    @cached_property
    def saved_reports(self):
        return self.collections.saved_reports

    def create_schedule(
        self,
        name=None,
        description=None,
        active=True,
        timer=None,
        email=None,
        email_options=None,
        cancel=False,
    ):
        view = navigate_to(self, "ScheduleReport")
        if email:
            email["emails_send"] = True
        schedule = self.appliance.collections.schedules.instantiate(
            name=name or self.menu_name,
            description=description or self.menu_name,
            active=active,
            report_filter={
                "filter_type": self.company_name,
                "subfilter_type": self.subtype,
                "report_type": self.menu_name,
            },
            timer=timer,
            email=email,
            email_options=email_options)
        view.fill(schedule.fill_dict)

        if cancel:
            view.cancel_button.click()
        else:
            view.add_button.click()
        view.flash.assert_no_error()

        assert schedule.exists
        return schedule

    def queue(self, wait_for_finish=False):
        view = navigate_to(self, "Details")
        view.report_info.queue_button.click()
        view.flash.assert_no_error()
        if wait_for_finish:
            # Get the queued_at value to always target the correct row
            if view.saved_reports.paginator.sorted_by['sortDir'] != "DESC":
                view.saved_reports.paginator.sort(sort_by="Queued At",
                                                  ascending=False)
            queued_at = view.saved_reports.table[0]["Queued At"].text

            def _get_state():
                row = view.saved_reports.table.row(queued_at=queued_at)
                status = row.status.text.strip().lower()
                assert status != "error"
                return status == "complete"

            wait_for(
                _get_state,
                delay=1,
                message="wait for report generation finished",
                fail_func=view.reload_button.click,
                num_sec=300,
            )
            view.reload_button.click()
        first_row = view.saved_reports.table[0]
        saved_report = self.saved_reports.instantiate(first_row.run_at.text,
                                                      first_row.queued_at.text,
                                                      self.is_candu)
        return saved_report

    @property
    def tree_path(self):
        return [
            "All Reports",
            self.type or self.company_name,
            self.subtype or "Custom",
            self.menu_name,
        ]

    @property
    def rest_api_entity(self):
        try:
            return self.appliance.rest_api.collections.reports.get(
                name=self.menu_name)
        except ValueError:
            raise RestLookupError(
                f"No report rest entity found matching name {self.menu_name}")
Ejemplo n.º 7
0
class Report(BaseEntity, Updateable):
    _param_name = ParamClassName('title')
    menu_name = attr.ib(default=None)
    title = attr.ib(default=None)
    company_name = attr.ib()
    type = attr.ib(default=None)
    subtype = attr.ib(default=None)
    base_report_on = attr.ib(default=None)
    report_fields = attr.ib(default=None)
    cancel_after = attr.ib(default=None)
    consolidation = attr.ib(default=None)
    formatting = attr.ib(default=None)
    styling = attr.ib(default=None)
    filter = attr.ib(default=None)
    filter_show_costs = attr.ib(default=None)
    filter_owner = attr.ib(default=None)
    filter_tag_cat = attr.ib(default=None)
    filter_tag_value = attr.ib(default=None)
    interval = attr.ib(default=None)
    interval_size = attr.ib(default=None)
    interval_end = attr.ib(default=None)
    sort = attr.ib(default=None)
    chart_type = attr.ib(default=None)
    top_values = attr.ib(default=None)
    sum_other = attr.ib(default=None)
    base_timeline_on = attr.ib(default=None)
    band_units = attr.ib(default=None)
    event_position = attr.ib(default=None)
    show_event_unit = attr.ib(default=None)
    show_event_count = attr.ib(default=None)
    summary = attr.ib(default=None)
    charts = attr.ib(default=None)
    timeline = attr.ib(default=None)
    is_candu = attr.ib(default=False)

    def __attrs_post_init__(self):
        self._collections = {'saved_reports': SavedReportsCollection}

    @company_name.default
    def company_name_default(self):
        return "My Company (All Groups)"

    def update(self, updates):
        view = navigate_to(self, "Edit")
        changed = view.fill(updates)
        if changed:
            view.save_button.click()
        else:
            view.cancel_button.click()
        view = self.create_view(ReportDetailsView,
                                override=updates,
                                wait='10s')
        view.flash.assert_no_error()
        if changed:
            view.flash.assert_message('Report "{}" was saved'.format(
                self.menu_name))
        else:
            view.flash.assert_message(
                'Edit of Report "{}" was cancelled by the user'.format(
                    self.menu_name))

    def copy(self):
        """ Copy a report via UI and return a copy of a Report object"""
        menu_name = "Copy of {}".format(self.menu_name)

        view = navigate_to(self, "Copy")
        view.add_button.click()
        self.create_view(AllReportsView, wait="5s")

        return self.appliance.collections.reports.instantiate(
            type=self.company_name,
            subtype="Custom",
            menu_name=menu_name,
            title=self.title,
        )

    def delete(self, cancel=False):
        view = navigate_to(self, "Details")
        node = view.reports.tree.expand_path("All Reports", self.company_name,
                                             "Custom")
        custom_reports_number = len(view.reports.tree.child_items(node))
        view.configuration.item_select("Delete this Report from the Database",
                                       handle_alert=not cancel)
        if cancel:
            view.wait_displayed()
            view.flash.assert_no_error()
        else:
            # This check is needed because after deleting the last custom report,
            # the whole "My Company (All EVM Groups)" branch in the tree will be removed.
            if custom_reports_number > 1:
                view = self.create_view(AllCustomReportsView, wait='5s')
            view.flash.assert_no_error()
            if not BZ(1561779, forced_streams=['5.9', '5.8']).blocks:
                view.flash.assert_message(
                    'Report "{}": Delete successful'.format(self.menu_name))

    @cached_property
    def saved_reports(self):
        return self.collections.saved_reports

    def create_schedule(self,
                        name=None,
                        description=None,
                        active=None,
                        timer=None,
                        from_email=None,
                        emails=None,
                        email_options=None):

        view = navigate_to(self, "ScheduleReport")
        view.fill({
            "name": name,
            "description": description,
            "active": active,
            "run": timer.get("run"),
            "time_zone": timer.get("time_zone"),
            "starting_date": timer.get("starting_date"),
            "hour": timer.get("hour"),
            "minute": timer.get("minute"),
            "emails_send": bool(emails),
            "from_email": from_email,
            "emails": emails,
            "send_if_empty": email_options.get("send_if_empty"),
            "send_txt": email_options.get("send_txt"),
            "send_csv": email_options.get("send_csv"),
            "send_pdf": email_options.get("send_pdf")
        })

        view.add_button.click()
        view.flash.assert_no_error()

        schedule = self.appliance.collections.schedules.instantiate(
            name=name or self.menu_name,
            description=description,
            filter=(self.company_name, self.subtype, self.menu_name),
            active=active,
            emails=emails,
            email_options=email_options,
        )

        assert schedule.exists
        return schedule

    def queue(self, wait_for_finish=False):
        view = navigate_to(self, "Details")
        view.report_info.queue_button.click()
        view.flash.assert_no_error()
        if wait_for_finish:
            # Get the queued_at value to always target the correct row
            if view.saved_reports.paginator.sorted_by['sortDir'] != "DESC":
                view.saved_reports.paginator.sort(sort_by="Queued At",
                                                  ascending=False)
            queued_at = view.saved_reports.table[0]["Queued At"].text

            def _get_state():
                row = view.saved_reports.table.row(queued_at=queued_at)
                status = row.status.text.strip().lower()
                assert status != "error"
                return status == "complete"

            wait_for(
                _get_state,
                delay=1,
                message="wait for report generation finished",
                fail_func=view.reload_button.click,
                num_sec=300,
            )
            view.reload_button.click()
        first_row = view.saved_reports.table[0]
        saved_report = self.saved_reports.instantiate(first_row.run_at.text,
                                                      first_row.queued_at.text,
                                                      self.is_candu)
        return saved_report
Ejemplo n.º 8
0
class BaseVM(Pretty, Updateable, PolicyProfileAssignable, WidgetasticTaggable,
             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']

    ###
    # 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 = VersionPick({
        '5.8': 'Remove selected items',
        '5.9': 'Remove selected items from Inventory'
    })
    REMOVE_SINGLE = VersionPick({
        '5.8': 'Remove Virtual Machine',
        '5.9': 'Remove Virtual Machine from Inventory'
    })
    RETIRE_DATE_FMT = VersionPick({
        '5.8': parsetime.american_minutes_with_utc,
        '5.9': parsetime.saved_report_title_format
    })
    _param_name = ParamClassName('name')
    DETAILS_VIEW_CLASS = None

    ###
    # 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

    ###
    # Methods
    #
    def check_compliance(self, timeout=240):
        """Initiates compliance check and waits for it to finish."""
        view = navigate_to(self, "Details")
        original_state = self.compliance_status
        view.toolbar.policy.item_select(
            "Check Compliance of Last Known Configuration", handle_alert=True)
        view.flash.assert_no_error()
        wait_for(lambda: self.compliance_status != original_state,
                 num_sec=timeout,
                 delay=5,
                 message="compliance of {} checked".format(self.name))

    @property
    def compliance_status(self):
        """Returns the title of the compliance SummaryTable. The title contains datetime so it can
        be compared.

        Returns:
            :py:class:`NoneType` if no title is present (no compliance checks before), otherwise str
        """
        view = navigate_to(self, "Details")
        view.browser.refresh()
        return self.get_detail(properties=("Compliance", "Status"))

    @property
    def compliant(self):
        """Check if the VM is compliant.

        Returns:
            :py:class:`bool`
        """
        text = self.compliance_status.strip().lower()
        if text.startswith("non-compliant"):
            return False
        elif text.startswith("compliant"):
            return True
        else:
            raise ValueError(
                "{} is not a known state for compliance".format(text))

    @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

    @property
    def vm_console(self):
        """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))

        return VMConsole(appliance_handle=appliance_handle,
                         console_handle=console_handle,
                         vm=self)

    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:
            view = navigate_to(self, 'Details')
            view.toolbar.configuration.item_select(self.REMOVE_SINGLE,
                                                   handle_alert=not cancel)
        else:
            view = navigate_to(self, 'All')
            self.find_quadicon().check()
            view.toolbar.configuration.item_select(self.REMOVE_SELECTED,
                                                   handle_alert=not 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"""
        view = self.load_details(refresh=True)
        if view.entities.lifecycle.get_text_of(
                'Retirement Date').lower() != 'never':
            try:
                return view.entities.lifecycle.get_text_of(
                    'Retirement state').lower() == 'retired'
            except NameError:
                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)

        if 'Grid View' != view.toolbar.view_selector.selected:
            view.toolbar.view_selector.select('Grid View')
        try:
            return view.entities.get_entity(name=self.name,
                                            surf_pages=True,
                                            use_search=use_search)
        except ItemNotFound:
            raise VmOrInstanceNotFound("VM '{}' not found in UI!".format(
                self.name))

    def get_detail(self, properties=None):
        """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.
        """
        view = navigate_to(self, 'Details')
        return getattr(view.entities, properties[0].lower().replace(
            ' ', '_')).get_text_of(properties[1])

    def open_console(self, console='VM Console', invokes_alert=None):
        """
        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.
        """
        # 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=invokes_alert)
        self.vm_console

    def open_details(self, properties=None):
        """Clicks on details infoblock"""
        view = navigate_to(self, 'Details')
        getattr(view.entities,
                properties[0].lower().replace(' ',
                                              '_')).click_at(properties[1])
        return self.create_view(VMPropertyDetailView)

    @classmethod
    def get_first_vm(cls, provider):
        """Get first VM/Instance."""
        # todo: move this to base provider ?
        view = navigate_to(cls, 'AllForProvider', provider=provider)
        return view.entities.get_first_entity()

    @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, from_any_provider=False):
        """Navigates to an VM's details page.

        Args:
            refresh: Refreshes the VM page if already there
            from_any_provider: Archived/Orphaned VMs need this
        """
        if from_any_provider:
            view = navigate_to(self, 'AnyProviderDetails', use_resetter=False)
        else:
            view = navigate_to(self, 'Details', use_resetter=False)
        if refresh:
            view.toolbar.reload.click()

        view.wait_displayed()
        return view

    def open_edit(self):
        """Loads up the edit page of the object."""
        return navigate_to(self, '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:
            view = self.load_details()
        else:
            view = navigate_to(self, 'All')
            self.find_quadicon(from_any_provider=from_any_provider).check()
        view.toolbar.configuration.item_select(
            "Refresh Relationships and Power States", handle_alert=not 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,
                        wait_for_task_result=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:
            view = self.load_details()
        else:
            view = navigate_to(self, 'All')
            self.find_quadicon().check()
        view.toolbar.configuration.item_select('Perform SmartState Analysis',
                                               handle_alert=not cancel)
        if wait_for_task_result:
            view = self.appliance.browser.create_view(TasksView)
            wait_for(lambda: is_vm_analysis_finished(self.name),
                     delay=15,
                     timeout="10m",
                     fail_func=view.reload.click)

    def wait_to_disappear(self, timeout=600):
        """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=self.browser.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
        """
        def _refresh():
            self.provider.refresh_provider_relationships()
            self.appliance.browser.widgetastic.browser.refresh(
            )  # strange because ViaUI

        wait_for(lambda: self.exists,
                 num_sec=timeout,
                 delay=5,
                 fail_func=_refresh,
                 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 instance ownership

        Args:
            user (str): username for ownership
            group (str): groupname for ownership
            click_cancel (bool): Whether to cancel form submission
            click_reset (bool): Whether to reset form after filling
        """
        view = navigate_to(self, 'SetOwnership')
        fill_result = view.form.fill({'user_name': user, 'group_name': group})
        if not fill_result:
            view.form.cancel_button.click()
            view = self.create_view(navigator.get_class(self, 'Details').VIEW)
            view.flash.assert_success_message(
                'Set Ownership was cancelled by the user')
            return

        # Only if the form changed
        if click_reset:
            view.form.reset_button.click()
            view.flash.assert_message('All changes have been reset', 'warning')
            # Cancel after reset
            assert view.form.is_displayed
            view.form.cancel_button.click()
        elif click_cancel:
            view.form.cancel_button.click()
            view.flash.assert_success_message(
                'Set Ownership was cancelled by the user')
        else:
            # save the form
            view.form.save_button.click()
            view = self.create_view(navigator.get_class(self, 'Details').VIEW)
            view.flash.assert_success_message(
                'Ownership saved for selected {}'.format(self.VM_TYPE))

    def unset_ownership(self):
        """Remove user ownership and return group to EvmGroup-Administrator"""
        view = navigate_to(self, 'SetOwnership')
        fill_result = view.form.fill({
            'user_name': '<No Owner>',
            'group_name': 'EvmGroup-administrator'
        })
        if fill_result:
            view.form.save_button.click()
            msg = 'Ownership saved for selected {}'.format(self.VM_TYPE)
        else:
            view.form.cancel_button.click()
            logger.warning('No change during unset_ownership')
            msg = 'Set Ownership was cancelled by the user'

        view = self.create_view(navigator.get_class(self, 'Details').VIEW)
        view.flash.assert_success_message(msg)
Ejemplo n.º 9
0
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))
Ejemplo n.º 10
0
class Server(BaseEntity, sentaku.modeling.ElementMixin):
    _param_name = ParamClassName('name')

    sid = attr.ib(default=1)

    address = sentaku.ContextualMethod()
    login = sentaku.ContextualMethod()
    login_admin = sentaku.ContextualMethod()
    logout = sentaku.ContextualMethod()
    update_password = sentaku.ContextualMethod()
    logged_in = sentaku.ContextualMethod()
    current_full_name = sentaku.ContextualMethod()
    current_username = sentaku.ContextualMethod()
    current_group_name = sentaku.ContextualMethod()
    group_names = sentaku.ContextualMethod()
    intel_name = VersionPicker({
        "5.11": "Overview",
        Version.lowest(): "Cloud Intel"
    })

    # zone = sentaku.ContextualProperty()
    # slave_servers = sentaku.ContextualProperty()

    @property
    def name(self):
        """Fetch the name from the master server api entity

        Returns:
            string if entity has the name attribute
            None if its missing
        """
        # empty string default for string building w/o None
        return getattr(self.appliance._rest_api_server(), 'name', '')

    @property
    def settings(self):
        from cfme.configure.configuration.server_settings import ServerInformation
        setting = ServerInformation(appliance=self.appliance)
        return setting

    @property
    def authentication(self):
        from cfme.configure.configuration.server_settings import AuthenticationSetting
        auth_settings = AuthenticationSetting(self.appliance)
        return auth_settings

    @property
    def collect_logs(self):
        from cfme.configure.configuration.diagnostics_settings import ServerCollectLog
        return ServerCollectLog(self.appliance)

    @property
    def zone(self):
        server_res = self.appliance.rest_api.collections.servers.find_by(
            id=self.sid)
        server, = server_res
        server.reload(attributes=['zone'])
        zone = server.zone
        zone_obj = self.appliance.collections.zones.instantiate(
            name=zone['name'], description=zone['description'], id=zone['id'])
        return zone_obj

    @property
    def slave_servers(self):
        return self.zone.collections.servers.filter({'slave': True}).all()

    @property
    def _api_settings_url(self):
        return '/'.join([
            self.appliance.rest_api.collections.servers._href,
            str(self.sid), 'settings'
        ])

    @property
    def advanced_settings(self):
        """GET servers/:id/settings api endpoint to query server configuration"""
        return self.appliance.rest_api.get(self._api_settings_url)

    def update_advanced_settings(self, settings_dict):
        """PATCH settings from the server's api/server/:id/settings endpoint

        Args:
            settings_dict: dictionary of the changes to be made to the yaml configuration
                       JSON dumps settings_dict to pass as raw hash data to rest_api session
        Raises:
            AssertionError: On an http result >=400 (RequestsResponse.ok)
        """
        # Calling the _session patch method because the core patch will wrap settings_dict in a list
        # Failing with some settings_dict, like 'authentication'
        # https://bugzilla.redhat.com/show_bug.cgi?id=1553394
        result = self.appliance.rest_api._session.patch(
            url=self._api_settings_url, data=json.dumps(settings_dict))
        assert result.ok

    def upload_custom_logo(self, file_type, file_data=None, enable=True):
        """
        This function can be used to upload custom logo or text and use them.

        Args:
            file_type (str) : Can be either of [logo, login_logo, brand, favicon, logintext]
            file_data (str) : Text data if file_type is logintext else image path to be uploaded
            enable (bool) : True to use the custom logo/text else False
        """
        view = navigate_to(self, "CustomLogos")
        try:
            logo_view = getattr(view.customlogos, file_type)
        except AttributeError:
            raise AttributeError(
                "File type not in ('logo', 'login_logo', 'brand', 'favicon', 'logintext)."
            )
        if file_data:
            if file_type == "logintext":
                logo_view.fill({"login_text": file_data})
            else:
                logo_view.fill({"image": file_data})
                logo_view.upload_button.click()
            view.flash.assert_no_error()
        logo_view.enable.fill(enable)
        view.customlogos.save_button.click()
        view.flash.assert_no_error()
Ejemplo n.º 11
0
class ComputeRate(Updateable, Pretty, Navigatable):
    """This class represents a Compute Chargeback rate.

    Example:
        .. code-block:: python

          >>> import cfme.intelligence.chargeback.rates as rates
          >>> rate = rates.ComputeRate(description=desc,
                    fields={'Used CPU':
                            {'per_time': 'Hourly', 'variable_rate': '3'},
                            'Used Disk I/O':
                            {'per_time': 'Hourly', 'variable_rate': '2'},
                            'Used Memory':
                            {'per_time': 'Hourly', 'variable_rate': '2'}})
          >>> rate.create()
          >>> rate.delete()

    Args:
        description: Rate description
        currency: Rate currency
        fields  : Rate fields
    """

    pretty_attrs = ['description']
    _param_name = ParamClassName('description')

    def __init__(
        self,
        description=None,
        currency=None,
        fields=None,
        appliance=None,
    ):
        Navigatable.__init__(self, appliance=appliance)
        self.description = description
        self.currency = currency
        self.fields = fields

    def __getitem__(self, name):
        return self.fields.get(name)

    def create(self):
        # Create a rate in UI
        view = navigate_to(self, 'New')
        view.fill_with(
            {
                'description': self.description,
                'currency': self.currency,
                'fields': self.fields
            },
            on_change=view.add_button,
            no_change=view.cancel_button)

        view.flash.assert_success_message(
            'Chargeback Rate "{}" was added'.format(self.description))

    def copy(self, *args, **kwargs):
        new_rate = ComputeRate(*args, **kwargs)
        add_view = navigate_to(self, 'Copy')
        add_view.fill_with(
            {
                'description': new_rate.description,
                'currency': new_rate.currency,
                'fields': new_rate.fields
            },
            on_change=add_view.add_button,
            no_change=add_view.cancel_button)

        return new_rate

    def update(self, updates):
        # Update a rate in UI
        view = navigate_to(self, 'Edit')
        view.fill_with(updates,
                       on_change=view.save_button,
                       no_change=view.cancel_button)

        view.flash.assert_success_message(
            'Chargeback Rate "{}" was saved'.format(
                updates.get('description')))

    def delete(self):
        # Delete a rate in UI
        view = navigate_to(self, 'Details')
        view.configuration.item_select('Remove from the VMDB',
                                       handle_alert=True)
        view.flash.assert_success_message(
            'Chargeback Rate "{}": Delete successful'.format(self.description))
Ejemplo n.º 12
0
class BaseVM(
        BaseEntity,
        Pretty,
        Updateable,
        PolicyProfileAssignable,
        Taggable,
        ConsoleMixin,
        CustomButtonEventsMixin,
):
    """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']

    ###
    # 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 = 'Remove selected items from Inventory'
    REMOVE_SINGLE = 'Remove Virtual Machine from Inventory'
    RETIRE_DATE_FMT = '%a, %d %b %Y %H:%M:%S +0000'
    RETIRE_DATE_MSG_FMT = '%m/%d/%y %H:%M UTC'
    _param_name = ParamClassName('name')
    DETAILS_VIEW_CLASS = None

    ###
    # Shared behaviour
    #
    PROVISION_CANCEL = 'Add of new VM Provision Request was cancelled by the user'
    PROVISION_START = (
        'VM Provision Request was Submitted, you will be notified when your VMs '
        'are ready')
    name = attr.ib()
    provider = attr.ib()

    def __new__(cls, *args, **kwargs):
        if cls in [BaseVM, VM, Template]:
            raise NotImplementedError('This class cannot be instantiated.')
        else:
            # magic {waves hands}
            return object.__new__(cls)

    ###
    # Properties
    #
    @property
    def is_vm(self):
        return not isinstance(self, _TemplateMixin)

    @property
    def quadicon_type(self):
        return self.QUADICON_TYPE

    ###
    # Methods
    #
    def check_compliance(self, timeout=240):
        """Initiates compliance check and waits for it to finish."""
        view = navigate_to(self, "Details")
        original_state = self.compliance_status
        view.toolbar.policy.item_select(
            "Check Compliance of Last Known Configuration", handle_alert=True)
        view.flash.assert_no_error()
        wait_for(lambda: self.compliance_status != original_state,
                 num_sec=timeout,
                 delay=5,
                 message=f"compliance of {self.name} checked")

    @property
    def compliance_status(self):
        """Returns the title of the compliance SummaryTable. The title contains datetime so it can
        be compared.

        Returns:
            :py:class:`NoneType` if no title is present (no compliance checks before), otherwise str
        """
        view = navigate_to(self, "Details")
        view.toolbar.reload.click()
        return view.entities.summary("Compliance").get_text_of("Status")

    @property
    def compliant(self):
        """Check if the VM is compliant.

        Returns:
            :py:class:`bool`
        """
        text = self.compliance_status.strip().lower()
        if text.startswith("non-compliant"):
            return False
        elif text.startswith("compliant"):
            return True
        else:
            raise ValueError(f"{text} is not a known state for compliance")

    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:
            view = navigate_to(self, 'Details')
            view.toolbar.configuration.item_select(self.REMOVE_SINGLE,
                                                   handle_alert=not cancel)
        else:
            view = navigate_to(self.parent, 'All')
            self.find_quadicon().ensure_checked()
            view.toolbar.configuration.item_select(self.REMOVE_SELECTED,
                                                   handle_alert=not cancel)

    def _fill_clone_form(self,
                         view,
                         email=None,
                         first_name=None,
                         last_name=None,
                         new_name=None,
                         provision_type=None):
        first_name = first_name or fauxfactory.gen_alphanumeric()
        last_name = last_name or fauxfactory.gen_alphanumeric()
        email = email or f"{first_name}@{last_name}.test"
        try:
            prov_data = cfme_data["management_systems"][
                self.provider.key]["provisioning"]
        except (KeyError, IndexError):
            raise ValueError(
                "You have to specify the correct options in cfme_data.yaml")

        provisioning_data = {
            'catalog': {
                'vm_name': new_name,
                'provision_type': provision_type
            },
            'request': {
                'email': email,
                'first_name': first_name,
                'last_name': last_name
            },
            'environment': {
                "host_name": {
                    'name': prov_data.get("host")
                },
                "datastore_name": {
                    "name": prov_data.get("datastore")
                }
            },
            'network': {
                'vlan': partial_match(prov_data.get("vlan"))
            },
        }
        view.form.fill_with(provisioning_data,
                            on_change=view.form.submit_button)

    @property
    def ip_address(self):
        """Fetches IP Address of VM

        First looks to see if any of the mgmt ips returned by 'all_ips' are pingable
        Then defaults to whatever mgmt.ip returns
        """
        return find_pingable(self.mgmt)

    @property
    def all_ip_addresses(self):
        """Fetches all IP Addresses of a VM, pingable or otherwise."""
        # TODO: Implement sentaku for this property with ViaMGMT impl
        view = navigate_to(self, "Details", use_resetter=False)
        try:
            return view.entities.summary('Properties').get_text_of(
                "IP Address")
        except NameError:
            # since some providers have plural 'Addresses'.
            return view.entities.summary('Properties').get_text_of(
                "IP Addresses").split(", ")

    @property
    def mac_address(self):
        """Fetches MAC Address of VM"""
        # TODO: We should update this with wrapanapi method when it becomes available.
        view = navigate_to(self, "Details", use_resetter=False)
        try:
            return view.entities.summary('Properties').get_text_of(
                "MAC Address")
        except NameError:
            # since some providers have plural 'Addresses'.
            return view.entities.summary('Properties').get_text_of(
                "MAC Addresses")

    @property
    def is_retired(self):
        """Check retirement status of vm"""
        view = navigate_to(self, "Details", use_resetter=False)
        if view.entities.summary('Lifecycle').get_text_of(
                'Retirement Date') != 'Never':
            try:
                status = view.entities.summary('Lifecycle').get_text_of(
                    'Retirement State')
                return status == 'Retired'
            except NameError:
                return False
        else:
            return False

    def find_quadicon(self,
                      from_any_provider=False,
                      from_archived_all=False,
                      from_orphaned_all=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: ItemNotFound
        """
        # TODO(all): Refactor this method replace it with vm methods like get_state
        if from_any_provider:
            view = navigate_to(self.parent, 'All')
        elif from_archived_all:
            view = navigate_to(
                self.appliance.provider_based_collection(self.provider),
                'ArchivedAll')
        elif from_orphaned_all:
            view = navigate_to(
                self.appliance.provider_based_collection(self.provider),
                'OrphanedAll')
        else:
            view = navigate_to(self, 'AllForProvider', use_resetter=False)

        view.toolbar.view_selector.select('Grid View')
        try:
            return view.entities.get_entity(name=self.name,
                                            surf_pages=True,
                                            use_search=use_search)
        except ItemNotFound:
            raise ItemNotFound(f"VM '{self.name}' not found in UI!")

    def open_console(self, console='VM Console', invokes_alert=None):
        """
        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.
        """
        if console not in ['VM Console', 'VMRC Console']:
            raise NotImplementedError(f'Not supported console type: {console}')

        view = navigate_to(self, 'Details')

        # dismiss_any_alerts() call closed subsequent alerts needed for vmrc
        # below code is needed to fix such issue
        try:
            view.browser.IGNORE_SUBSEQUENT_ALERTS = True
            # Click console button given by type
            view.toolbar.access.item_select(console,
                                            handle_alert=invokes_alert)
        finally:
            view.browser.IGNORE_SUBSEQUENT_ALERTS = False
        self.vm_console

    def open_details(self, properties=None):
        """Clicks on details infoblock"""
        view = navigate_to(self, 'Details')
        view.entities.summary(properties[0]).click_at(properties[1])
        return self.create_view(VMPropertyDetailView)

    @property
    def last_analysed(self):
        """Returns the contents of the ``Last Analysed`` field in summary"""
        view = navigate_to(self, "Details")
        view.toolbar.reload.click()
        return view.entities.summary("Lifecycle").get_text_of(
            "Last Analyzed").strip()

    def load_details(self, refresh=False, from_any_provider=False):
        """Navigates to an VM's details page.

        Args:
            refresh: Refreshes the VM page if already there
            from_any_provider: Archived/Orphaned VMs need this
        """
        if from_any_provider:
            view = navigate_to(self, 'AnyProviderDetails', use_resetter=False)
        else:
            view = navigate_to(self, 'Details', use_resetter=False)
        if refresh:
            view.toolbar.reload.click()

        view.wait_displayed()
        return view

    def open_edit(self):
        """Loads up the edit page of the object."""
        return navigate_to(self, '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:
            view = navigate_to(self, 'Details', use_resetter=False)
        else:
            view = navigate_to(self.parent, 'All')
            self.find_quadicon(
                from_any_provider=from_any_provider).ensure_checked()
        view.toolbar.configuration.item_select(
            "Refresh Relationships and Power States", handle_alert=not cancel)

    @property
    def retirement_date(self):
        """Returns the retirement date of the selected machine, or 'Never'

        Returns:
            :py:class:`str` object
        """
        view = navigate_to(self, "Details")
        return view.entities.summary("Lifecycle").get_text_of(
            "Retirement Date").strip()

    def smartstate_scan(self,
                        cancel=False,
                        from_details=False,
                        wait_for_task_result=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:
            view = navigate_to(self, 'Details', use_resetter=False)
        else:
            view = navigate_to(self.parent, 'All')
            self.find_quadicon().ensure_checked()
        view.toolbar.configuration.item_select('Perform SmartState Analysis',
                                               handle_alert=not cancel)
        if wait_for_task_result:
            task = self.appliance.collections.tasks.instantiate(
                name=f'Scan from Vm {self.name}', tab='AllTasks')
            task.wait_for_finished()
            return task

    def wait_to_disappear(self, timeout=600):
        """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=5,
                 fail_func=self.browser.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
        """
        def _refresh():
            self.appliance.browser.widgetastic.browser.refresh(
            )  # strange because ViaUI
            self.provider.refresh_provider_relationships(wait=600)

        wait_for(lambda: self.exists,
                 num_sec=timeout,
                 delay=5,
                 fail_func=_refresh,
                 message="wait for vm to appear")
        if load_details:
            navigate_to(self, "Details", use_resetter=False)

    def set_ownership(self, user=None, group=None, cancel=False, reset=False):
        """Set instance ownership

        Args:
            user (User): user object for ownership
            group (Group): group object for ownership
            cancel (bool): Whether to cancel form submission
            reset (bool): Whether to reset form after filling
        """
        view = navigate_to(self, 'SetOwnership', wait_for_view=0)
        fill_result = view.form.fill({
            'user_name':
            user.name if user else None,
            'group_name':
            group.description if group else group
        })
        if not fill_result:
            view.form.cancel_button.click()
            view = self.create_view(navigator.get_class(self, 'Details').VIEW)
            view.flash.assert_success_message(
                'Set Ownership was cancelled by the user')
            return

        # Only if the form changed
        if reset:
            view.form.reset_button.click()
            view.flash.assert_message('All changes have been reset', 'warning')
            # Cancel after reset
            assert view.form.is_displayed
            view.form.cancel_button.click()
        elif cancel:
            view.form.cancel_button.click()
            view.flash.assert_success_message(
                'Set Ownership was cancelled by the user')
        else:
            # save the form
            view.form.save_button.click()
            view = self.create_view(navigator.get_class(self, 'Details').VIEW)
            view.flash.assert_success_message(
                'Ownership saved for selected {}'.format(self.VM_TYPE))

    def unset_ownership(self):
        """Remove user ownership and return group to EvmGroup-Administrator"""
        view = navigate_to(self, 'SetOwnership', wait_for_view=0)
        fill_result = view.form.fill({
            'user_name': '<No Owner>',
            'group_name': 'EvmGroup-administrator'
        })
        if fill_result:
            view.form.save_button.click()
            msg = f'Ownership saved for selected {self.VM_TYPE}'
        else:
            view.form.cancel_button.click()
            logger.warning('No change during unset_ownership')
            msg = 'Set Ownership was cancelled by the user'

        view = self.create_view(navigator.get_class(self, 'Details').VIEW)
        view.flash.assert_success_message(msg)

    def rename(self, new_vm_name, cancel=False, reset=False):
        """Rename the VM

        Args:
            new_vm_name: object for renaming vm
            cancel (bool): Whether to cancel form submission
            reset (bool): Whether to reset form after filling
        """
        view = navigate_to(self, 'Rename')
        changed = view.vm_name.fill(new_vm_name)
        if changed:
            if reset:
                view.reset_button.click()
                view.flash.assert_no_error()
                view.cancel_button.click()
            else:
                # save the form
                view.save_button.click()
                view.flash.assert_no_error()
                self.name = new_vm_name
                return self
        if cancel:
            view.cancel_button.click()
            view.flash.assert_no_error()

    @property
    def rest_api_entity(self):
        # This will not work if the VM has retired since we filter using provider
        collection = "instances" if self.VM_TYPE == "Instance" else "vms"
        try:
            return (getattr(
                self.appliance.rest_api.collections, collection).filter(
                    Q("name", "=", self.name)
                    & Q("ems_id", "=", self.provider.rest_api_entity.id)).
                    resources[0])
        except IndexError:
            raise RestLookupError(
                f"No {self.VM_TYPE} rest entity found matching name {self.name}"
            )

    def wait_for_power_state_change_rest(self,
                                         desired_state,
                                         timeout=1200,
                                         delay=45):
        """Wait for a VM/Instance power state to change to a desired state.

        Args:
            desired_state: A string indicating the desired state
            timeout: Specify amount of time (in seconds) to wait until TimedOutError is raised

        TODO: Change this to use Sentaku
        """
        return wait_for(
            lambda: self.rest_api_entity.power_state == desired_state,
            fail_func=self.rest_api_entity.reload,
            num_sec=timeout,
            delay=delay,
            handle_exception=True,
            message=
            f"Waiting for VM/Instance power state to change to {desired_state}"
        ).out
Ejemplo n.º 13
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.toolbar.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))
        ]
Ejemplo n.º 14
0
class BaseCondition(BaseEntity, Updateable, Pretty):

    TREE_NODE = None
    PRETTY = None
    FIELD_VALUE = None
    _param_name = ParamClassName('description')

    def __init__(self,
                 collection,
                 description,
                 expression=None,
                 scope=None,
                 notes=None):
        self.collection = collection
        self.appliance = self.collection.appliance
        self.description = description
        self.expression = expression
        self.scope = scope
        self.notes = notes

    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, override=updates)
        assert view.is_displayed
        view.flash.assert_success_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_success_message(
                'Condition "{}": Delete successful'.format(self.description))

    def read_expression(self):
        view = navigate_to(self, "Details")
        assert view.is_displayed
        return view.expression.text

    def read_scope(self):
        view = navigate_to(self, "Details")
        assert view.is_displayed
        return view.scope.text

    @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.client["conditions"]
        return self.appliance.db.client.session\
            .query(conditions.description)\
            .filter(conditions.description == self.description)\
            .count() > 0
Ejemplo n.º 15
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.

        TODO This should be refactored as it's done `Host.check_compliance`. It shouldn't return
        anything. `compliant` property should use `compliance_status`.

        """
        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)

        if 'Grid View' != view.toolbar.view_selector.selected:
            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(cls, provider):
        """Get first VM/Instance."""
        # todo: move this to base provider ?
        view = navigate_to(cls, 'AllForProvider', provider=provider)
        return view.entities.get_first_entity()

    @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
        """
        def _refresh():
            self.provider.refresh_provider_relationships()
            self.appliance.browser.widgetastic.browser.refresh(
            )  # strange because ViaUI

        wait_for(lambda: self.exists,
                 num_sec=timeout,
                 delay=5,
                 fail_func=_refresh,
                 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))
Ejemplo n.º 16
0
class BasePolicy(BaseEntity, Updateable, 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')

    description = attr.ib()
    active = attr.ib(default=None)
    scope = attr.ib(default=None)
    notes = attr.ib(default=None)

    def __str__(self):
        return self.description

    @property
    def name_for_policy_profile(self):
        return "{} {}: {}".format(self.PRETTY, self.TYPE, 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()
        view = self.create_view(PolicyDetailsView, override=updates)
        wait_for(lambda: view.is_displayed,
                 timeout=10,
                 message="waits until the 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:
            # no redirection
            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_success_message(
            'Policy "Copy of {}" was added'.format(self.description))
        policy_copy = copy(self)
        policy_copy.description = "Copy of {}".format(self.description)
        return policy_copy

    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_success_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()
            flash_message = 'Policy "{}" was saved'.format(self.description)
        else:
            view.cancel_button.click()
            flash_message = 'Edit of Policy "{}" was cancelled by the user'.format(
                self.description)
        view.flash.assert_success_message(flash_message)

    def is_condition_assigned(self, condition):
        condition.context_policy = self
        view = navigate_to(condition, "Details in policy")
        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.
        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, tuple, set)):
            true_actions.extend(actions)
        elif isinstance(actions, dict):
            for action, is_true in actions.items():
                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 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.context_event = event
        view = navigate_to(self, "Event Details")
        assert view.is_displayed
        view.toolbar.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_success_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))
        ]

    def assigned_actions_to_event(self, event):
        self.context_event = event
        view = navigate_to(self, "Event Details")
        try:
            true_actions = [
                row.description.text for row in view.true_actions.rows()
            ]
        except NoSuchElementException:
            true_actions = []
        try:
            false_actions = [
                row.description.text for row in view.false_actions.rows()
            ]
        except NoSuchElementException:
            false_actions = []
        return true_actions + false_actions
Ejemplo n.º 17
0
class ConfigManager(Updateable, Pretty, Navigatable):
    """
    This is base class for Configuration manager objects (Red Hat Satellite, Foreman, Ansible Tower)

    Args:
        name: Name of the config. manager
        url: URL, hostname or IP of the config. manager
        ssl: Boolean value; `True` if SSL certificate validity should be checked, `False` otherwise
        credentials: Credentials to access the config. manager
        key: Key to access the cfme_data yaml data (same as `name` if not specified)

    Usage:
        Use Satellite or AnsibleTower classes instead.
    """

    pretty_attr = ['name', 'url']
    _param_name = ParamClassName('name')
    type = None
    refresh_flash_msg = 'Refresh Provider initiated for 1 provider'

    def __init__(self, name=None, url=None, ssl=None, credentials=None, key=None, appliance=None):
        Navigatable.__init__(self, appliance=appliance)
        self.name = name
        self.url = url
        self.ssl = ssl
        self.credentials = credentials
        self.key = key or name

    class Credential(BaseCredential, Updateable):
        pass

    @property
    def ui_name(self):
        """Return the name used in the UI"""
        if self.type == 'Ansible Tower':
            return '{} Automation Manager'.format(self.name)
        else:
            return '{} Configuration Manager'.format(self.name)

    def create(self, cancel=False, validate_credentials=True, validate=True, force=False):
        """Creates the manager through UI

        Args:
            cancel (bool): Whether to cancel out of the creation.  The cancel is done
                after all the information present in the manager has been filled in the UI.
            validate_credentials (bool): Whether to validate credentials - if True and the
                credentials are invalid, an error will be raised.
            validate (bool): Whether we want to wait for the manager's data to load
                and show up in it's detail page. True will also wait, False will only set it up.
            force (bool): Whether to force the creation even if the manager already exists.
                True will try anyway; False will check for its existence and leave, if present.
        """
        def config_profiles_loaded():
            # Workaround - without this, validation of provider failed
            config_profiles_names = [prof.name for prof in self.config_profiles]
            logger.info(
                "UI: %s\nYAML: %s",
                set(config_profiles_names), set(self.yaml_data['config_profiles']))
            # Just validate any profiles from yaml are in UI - not all are displayed
            return any(
                [cp in config_profiles_names for cp in self.yaml_data['config_profiles']])

        if not force and self.exists:
            return
        form_dict = self.__dict__
        form_dict.update(self.credentials.view_value_mapping)
        if self.appliance.version < '5.8':
            form_dict['provider_type'] = self.type
        view = navigate_to(self, 'Add')
        view.entities.form.fill(form_dict)
        if validate_credentials:
            view.entities.form.validate.click()
            view.flash.assert_success_message('Credential validation was successful')
        if cancel:
            view.entities.cancel.click()
            view.flash.assert_success_message('Add of Provider was cancelled by the user')
        else:
            view.entities.add.wait_displayed('2s')
            view.entities.add.click()
            success_message = '{} Provider "{}" was added'.format(self.type, self.name)
            view.flash.assert_success_message(success_message)
            view.flash.assert_success_message(self.refresh_flash_msg)
            if validate:
                try:
                    self.yaml_data['config_profiles']
                except KeyError as e:
                    logger.exception(e)
                    raise

                wait_for(
                    config_profiles_loaded,
                    fail_func=self.refresh_relationships,
                    handle_exception=True,
                    num_sec=180, delay=30)

    def update(self, updates, cancel=False, validate_credentials=False):
        """Updates the manager through UI

        args:
            updates (dict): Data to change.
            cancel (bool): Whether to cancel out of the update.  The cancel is done
                after all the new information has been filled in the UI.
            validate_credentials (bool): Whether to validate credentials - if True and the
                credentials are invalid, an error will be raised.

        Note:
            utils.update use is recommended over use of this method.
        """
        view = navigate_to(self, 'Edit')
        view.entities.form.fill(updates)
        if validate_credentials:
            view.entities.form.validate.click()
            view.flash.assert_success_message('Credential validation was successful')
        if cancel:
            view.entities.cancel.click()
            view.flash.assert_success_message('Edit of Provider was cancelled by the user')
        else:
            view.entities.save.click()
            view.flash.assert_success_message(
                '{} Provider "{}" was updated'.format(self.type, updates['name'] or self.name))
            self.__dict__.update(**updates)

    def delete(self, cancel=False, wait_deleted=True, force=False):
        """Deletes the manager through UI

        Args:
            cancel (bool): Whether to cancel out of the deletion, when the alert pops up.
            wait_deleted (bool): Whether we want to wait for the manager to disappear from the UI.
                True will wait; False will only delete it and move on.
            force (bool): Whether to try to delete the manager even though it doesn't exist.
                True will try to delete it anyway; False will check for its existence and leave,
                if not present.
        """
        if not force and not self.exists:
            return
        view = navigate_to(self, 'All')
        view.toolbar.view_selector.select('List View')
        provider_entity = view.entities.get_entities_by_keys(provider_name=self.ui_name)
        provider_entity[0].check()
        remove_item = 'Remove selected items from Inventory'
        view.toolbar.configuration.item_select(remove_item, handle_alert=not cancel)
        if not cancel:
            view.flash.assert_success_message('Delete initiated for 1 Provider')
            if wait_deleted:
                wait_for(
                    # check the provider is not listed in all providers anymore
                    func=lambda: not view.entities.get_entities_by_keys(
                        provider_name=self.ui_name
                    ), delay=15, fail_func=view.toolbar.refresh.click, num_sec=5 * 60
                )
                # check the provider is indeed deleted
                assert not self.exists

    @property
    def rest_api_entity(self):
        """Returns the rest entity of config manager provider"""
        # Since config manager provider is slightly different from other normal providers,
        # we cannot obtain it's rest entity the normal way, instead, we use Entity class
        # of manageiq_client library to instantiate the rest entity.
        provider_id = self.appliance.rest_api.collections.providers.get(
            name=self.ui_name
        ).provider_id

        return RestEntity(
            self.appliance.rest_api.collections.providers,
            data={
                "href": self.appliance.url_path(
                    "/api/providers/{}?provider_class=provider".format(provider_id)
                )
            },
        )

    def create_rest(self):
        """Create the config manager in CFME using REST"""
        if "ansible_tower" in self.key:
            config_type = "AnsibleTower"
        elif "satellite" in self.key:
            config_type = "Foreman"

        payload = {
            "type": "ManageIQ::Providers::{}::Provider".format(config_type),
            "url": self.url,
            "name": self.name,
            "verify_ssl": self.ssl,
            "credentials": {
                "userid": self.credentials.view_value_mapping["username"],
                "password": self.credentials.view_value_mapping["password"],
            },
        }
        try:
            self.appliance.rest_api.post(
                api_endpoint_url=self.appliance.url_path(
                    "/api/providers/?provider_class=provider"
                ),
                **payload
            )
        except APIException as err:
            raise AssertionError("Provider wasn't added: {}".format(err))

        response = self.appliance.rest_api.response
        if not response:
            raise AssertionError(
                "Provider wasn't added, status code {}".format(response.status_code)
            )

        assert_response(self.appliance)
        return True

    def delete_rest(self):
        """Deletes the config manager from CFME using REST"""
        try:
            self.rest_api_entity.action.delete()
        except APIException as err:
            raise AssertionError("Provider wasn't deleted: {}".format(err))

        response = self.appliance.rest_api.response
        if not response:
            raise AssertionError(
                "Provider wasn't deleted, status code {}".format(response.status_code)
            )

    @property
    def exists(self):
        """Returns whether the manager exists in the UI or not"""
        try:
            navigate_to(self, 'Details')
        except NoSuchElementException:
            return False
        return True

    def refresh_relationships(self, cancel=False):
        """Refreshes relationships and power states of this manager"""
        view = navigate_to(self, 'All')
        view.toolbar.view_selector.select('List View')
        provider_entity = view.entities.get_entities_by_keys(provider_name=self.ui_name)[0]
        provider_entity.check()
        if view.toolbar.configuration.item_enabled('Refresh Relationships and Power states'):
            view.toolbar.configuration.item_select('Refresh Relationships and Power states',
                                                   handle_alert=not cancel)
        if not cancel:
            view.flash.assert_success_message(self.refresh_flash_msg)

    @property
    def config_profiles(self):
        """Returns 'ConfigProfile' configuration profiles (hostgroups) available on this manager"""
        view = navigate_to(self, 'Details')
        # TODO - remove it later.Workaround for BZ 1452425
        view.toolbar.view_selector.select('List View')
        view.toolbar.refresh.click()
        wait_for(lambda: view.entities.elements.is_displayed, fail_func=view.toolbar.refresh.click,
                 handle_exception=True, num_sec=60, delay=5)
        config_profiles = []
        for row in view.entities.elements:
            if self.type == 'Ansible Tower':
                name = row.name.text
            else:
                name = row.description.text
            if 'unassigned' in name.lower():
                continue
            config_profiles.append(ConfigProfile(name=name, manager=self))
        return config_profiles

    @property
    def systems(self):
        """Returns 'ConfigSystem' configured systems (hosts) available on this manager"""
        return sum([prof.systems for prof in self.config_profiles])

    @property
    def yaml_data(self):
        """Returns yaml data for this manager"""
        return conf.cfme_data.configuration_managers[self.key]

    @classmethod
    def load_from_yaml(cls, key):
        """Returns 'ConfigManager' object loaded from yamls, based on its key"""
        data = conf.cfme_data.configuration_managers[key]
        creds = conf.credentials[data['credentials']]
        return cls(
            name=data['name'],
            url=data['url'],
            ssl=data['ssl'],
            credentials=cls.Credential(
                principal=creds['username'], secret=creds['password']),
            key=key)

    @property
    def quad_name(self):
        if self.type == 'Ansible Tower':
            return '{} Automation Manager'.format(self.name)
        else:
            return '{} Configuration Manager'.format(self.name)
Ejemplo n.º 18
0
class ConfigManager(Updateable, Pretty, Navigatable):
    """
    This is base class for Configuration manager objects (Red Hat Satellite, Foreman, Ansible Tower)

    Args:
        name: Name of the config. manager
        url: URL, hostname or IP of the config. manager
        ssl: Boolean value; `True` if SSL certificate validity should be checked, `False` otherwise
        credentials: Credentials to access the config. manager
        key: Key to access the cfme_data yaml data (same as `name` if not specified)

    Usage:
        Use Satellite or AnsibleTower classes instead.
    """

    pretty_attr = ['name', 'url']
    _param_name = ParamClassName('name')
    type = None
    refresh_flash_msg = 'Refresh Provider initiated for 1 provider'

    def __init__(self,
                 name=None,
                 url=None,
                 ssl=None,
                 credentials=None,
                 key=None,
                 appliance=None):
        Navigatable.__init__(self, appliance=appliance)
        self.name = name
        self.url = url
        self.ssl = ssl
        self.credentials = credentials
        self.key = key or name

    class Credential(BaseCredential, Updateable):
        pass

    @property
    def ui_name(self):
        """Return the name used in the UI"""
        return '{name} Automation Manager'.format(name=self.name)

    def create(self,
               cancel=False,
               validate_credentials=True,
               validate=True,
               force=False):
        """Creates the manager through UI

        Args:
            cancel (bool): Whether to cancel out of the creation.  The cancel is done
                after all the information present in the manager has been filled in the UI.
            validate_credentials (bool): Whether to validate credentials - if True and the
                credentials are invalid, an error will be raised.
            validate (bool): Whether we want to wait for the manager's data to load
                and show up in it's detail page. True will also wait, False will only set it up.
            force (bool): Whether to force the creation even if the manager already exists.
                True will try anyway; False will check for its existence and leave, if present.
        """
        def config_profiles_loaded():
            # Workaround - without this, validation of provider failed
            config_profiles_names = [
                prof.name for prof in self.config_profiles
            ]
            logger.info("UI: %s\nYAML: %s", set(config_profiles_names),
                        set(self.yaml_data['config_profiles']))
            return all([
                cp in config_profiles_names
                for cp in self.yaml_data['config_profiles']
            ])

        if not force and self.exists:
            return
        form_dict = self.__dict__
        form_dict.update(self.credentials.view_value_mapping)
        if self.appliance.version < '5.8':
            form_dict['provider_type'] = self.type
        view = navigate_to(self, 'Add')
        view.entities.form.fill(form_dict)
        if validate_credentials:
            view.entities.form.validate.click()
            view.entities.flash.assert_success_message(
                'Credential validation was successful')
        if cancel:
            view.entities.cancel.click()
            view.entities.flash.assert_success_message(
                'Add of Provider was cancelled by the user')
        else:
            view.entities.add.click()
            success_message = '{} Provider "{}" was added'.format(
                self.type, self.name)
            view.entities.flash.assert_success_message(success_message)
            view.entities.flash.assert_success_message(self.refresh_flash_msg)
            if validate:
                try:
                    self.yaml_data['config_profiles']
                except KeyError as e:
                    logger.exception(e)
                    raise

                wait_for(config_profiles_loaded,
                         fail_func=self.refresh_relationships,
                         handle_exception=True,
                         num_sec=180,
                         delay=30)

    def update(self, updates, cancel=False, validate_credentials=False):
        """Updates the manager through UI

        args:
            updates (dict): Data to change.
            cancel (bool): Whether to cancel out of the update.  The cancel is done
                after all the new information has been filled in the UI.
            validate_credentials (bool): Whether to validate credentials - if True and the
                credentials are invalid, an error will be raised.

        Note:
            utils.update use is recommended over use of this method.
        """
        view = navigate_to(self, 'Edit')
        view.entities.form.fill(updates)
        if validate_credentials:
            view.entities.form.validate.click()
            view.entities.flash.assert_success_message(
                'Credential validation was successful')
        if cancel:
            view.entities.cancel.click()
            view.entities.flash.assert_success_message(
                'Edit of Provider was cancelled by the user')
        else:
            view.entities.save.click()
            view.entities.flash.assert_success_message(
                '{} Provider "{}" was updated'.format(
                    self.type, updates['name'] or self.name))
            self.__dict__.update(**updates)

    def delete(self, cancel=False, wait_deleted=True, force=False):
        """Deletes the manager through UI

        Args:
            cancel (bool): Whether to cancel out of the deletion, when the alert pops up.
            wait_deleted (bool): Whether we want to wait for the manager to disappear from the UI.
                True will wait; False will only delete it and move on.
            force (bool): Whether to try to delete the manager even though it doesn't exist.
                True will try to delete it anyway; False will check for its existence and leave,
                if not present.
        """
        if not force and not self.exists:
            return
        view = navigate_to(self, 'All')
        view.toolbar.view_selector.select('List View')
        row = view.entities.paginator.find_row_on_pages(
            view.entities.elements, provider_name=self.ui_name)
        row[0].check()
        remove_item = VersionPick({
            '5.8': 'Remove selected items',
            '5.9': 'Remove selected items from Inventory'
        }).pick(self.appliance.version)
        view.toolbar.configuration.item_select(remove_item,
                                               handle_alert=not cancel)
        if not cancel:
            view.entities.flash.assert_success_message(
                'Delete initiated for 1 Provider')
            if wait_deleted:
                wait_for(func=lambda: self.exists,
                         fail_condition=True,
                         delay=15,
                         num_sec=60)

    @property
    def exists(self):
        """Returns whether the manager exists in the UI or not"""
        view = navigate_to(self, 'All')
        view.toolbar.view_selector.select('List View')
        try:
            view.entities.paginator.find_row_on_pages(
                view.entities.elements, provider_name=self.ui_name)
            return True
        except NoSuchElementException:
            pass
        return False

    def refresh_relationships(self, cancel=False):
        """Refreshes relationships and power states of this manager"""
        view = navigate_to(self, 'All')
        view.toolbar.view_selector.select('List View')
        row = view.entities.paginator.find_row_on_pages(
            view.entities.elements, provider_name=self.ui_name)
        row[0].check()
        if view.toolbar.configuration.item_enabled(
                'Refresh Relationships and Power states'):
            view.toolbar.configuration.item_select(
                'Refresh Relationships and Power states',
                handle_alert=not cancel)
        if not cancel:
            view.entities.flash.assert_success_message(self.refresh_flash_msg)

    @property
    def config_profiles(self):
        """Returns 'ConfigProfile' configuration profiles (hostgroups) available on this manager"""
        view = navigate_to(self, 'Details')
        # TODO - remove it later.Workaround for BZ 1452425
        view.toolbar.view_selector.select('List View')
        view.toolbar.refresh.click()
        wait_for(lambda: view.entities.elements.is_displayed,
                 fail_func=view.toolbar.refresh.click,
                 handle_exception=True,
                 num_sec=60,
                 delay=5)
        config_profiles = []
        for row in view.entities.elements:
            if self.type == 'Ansible Tower':
                name = row.name.text
            else:
                name = row.description.text
            if 'unassigned' in name.lower():
                continue
            config_profiles.append(ConfigProfile(name=name, manager=self))
        return config_profiles

    @property
    def systems(self):
        """Returns 'ConfigSystem' configured systems (hosts) available on this manager"""
        return reduce(lambda x, y: x + y,
                      [prof.systems for prof in self.config_profiles])

    @property
    def yaml_data(self):
        """Returns yaml data for this manager"""
        return conf.cfme_data.configuration_managers[self.key]

    @classmethod
    def load_from_yaml(cls, key):
        """Returns 'ConfigManager' object loaded from yamls, based on its key"""
        data = conf.cfme_data.configuration_managers[key]
        creds = conf.credentials[data['credentials']]
        return cls(name=data['name'],
                   url=data['url'],
                   ssl=data['ssl'],
                   credentials=cls.Credential(principal=creds['username'],
                                              secret=creds['password']),
                   key=key)

    @property
    def quad_name(self):
        if self.type == 'Ansible Tower':
            return '{} Automation Manager'.format(self.name)
        else:
            return '{} Configuration Manager'.format(self.name)
Ejemplo n.º 19
0
class ConfigManagerProvider(BaseProvider, Updateable, Pretty):
    """
    This is base class for Configuration manager objects (Red Hat Satellite, Foreman, Ansible Tower)

    Args:
        name: Name of the config. manager
        url: URL, hostname or IP of the config. manager
        credentials: Credentials to access the config. manager
        key: Key to access the cfme_data yaml data (same as `name` if not specified)

    Usage:
        Use Satellite or AnsibleTower classes instead.
    """

    pretty_attr = ['name', 'key']
    _param_name = ParamClassName('name')
    category = "config_manager"
    refresh_flash_msg = 'Refresh Provider initiated for 1 provider'
    name = attr.ib(default=None)
    url = attr.ib(default=None)
    credentials = attr.ib(default=None)
    key = attr.ib(default=None)
    ui_type = None

    _collections = {"config_profiles": ConfigProfilesCollection}

    class Credential(BaseCredential, Updateable):
        pass

    @property
    def exists(self):
        """ Returns ``True`` if a provider of the same name exists on the appliance
            This overwrite of BaseProvider exists is necessary because MIQ appends
            Configuration Manager to the provider name
        """
        for name in self.appliance.managed_provider_names:
            if self.name in name:
                return True
        return False

    def create(self,
               cancel=False,
               validate_credentials=True,
               validate=True,
               force=False,
               **kwargs):
        """Creates the manager through UI

        Args:
            cancel (bool): Whether to cancel out of the creation.  The cancel is done
                after all the information present in the manager has been filled in the UI.
            validate_credentials (bool): Whether to validate credentials - if True and the
                credentials are invalid, an error will be raised.
            validate (bool): Whether we want to wait for the manager's data to load
                and show up in it's detail page. True will also wait, False will only set it up.
            force (bool): Whether to force the creation even if the manager already exists.
                True will try anyway; False will check for its existence and leave, if present.
        """
        def config_profiles_loaded():
            # Workaround - without this, validation of provider failed
            config_profiles_names = [
                prof.name for prof in self.config_profiles
            ]
            logger.info("UI: %s\nYAML: %s", set(config_profiles_names),
                        set(self.data['config_profiles']))
            # Just validate any profiles from yaml are in UI - not all are displayed
            return any([
                cp in config_profiles_names
                for cp in self.data['config_profiles']
            ])

        if not force and self.exists:
            return

        form_dict = self.__dict__
        form_dict.update(self.credentials.view_value_mapping)

        view = navigate_to(self, 'Add')
        view.entities.form.fill(form_dict)
        if validate_credentials:
            view.entities.form.validate.click()
            view.flash.assert_success_message(
                'Credential validation was successful')
        if cancel:
            view.entities.cancel.click()
            view.flash.assert_success_message(
                'Add of Provider was cancelled by the user')
        else:
            view.entities.add.wait_displayed('2s')
            view.entities.add.click()
            success_message = f'{self.ui_type} Provider "{self.name}" was added'
            view.flash.assert_success_message(success_message)
            view.flash.assert_success_message(self.refresh_flash_msg)
            if validate:
                try:
                    self.data['config_profiles']
                except KeyError as e:
                    logger.exception(e)
                    raise

                wait_for(config_profiles_loaded,
                         fail_func=self.refresh_relationships,
                         handle_exception=True,
                         num_sec=180,
                         delay=30)

    def update(self, updates, cancel=False, validate_credentials=False):
        """Updates the manager through UI

        args:
            updates (dict): Data to change.
            cancel (bool): Whether to cancel out of the update.  The cancel is done
                after all the new information has been filled in the UI.
            validate_credentials (bool): Whether to validate credentials - if True and the
                credentials are invalid, an error will be raised.

        Note:
            utils.update use is recommended over use of this method.
        """
        view = navigate_to(self, 'Edit')
        view.entities.form.fill(updates)
        if validate_credentials:
            view.entities.form.validate.click()
            view.flash.assert_success_message(
                'Credential validation was successful')
        if cancel:
            view.entities.cancel.click()
            view.flash.assert_success_message(
                'Edit of Provider was cancelled by the user')
        else:
            view.entities.save.click()
            view.flash.assert_success_message(
                '{} Provider "{}" was updated'.format(
                    self.ui_type, updates['name'] or self.name))
            self.__dict__.update(**updates)

    def delete(self, cancel=False, wait_deleted=True, force=False):
        """Deletes the manager through UI

        Args:
            cancel (bool): Whether to cancel out of the deletion, when the alert pops up.
            wait_deleted (bool): Whether we want to wait for the manager to disappear from the UI.
                True will wait; False will only delete it and move on.
            force (bool): Whether to try to delete the manager even though it doesn't exist.
                True will try to delete it anyway; False will check for its existence and leave,
                if not present.
        """
        if not force and not self.exists:
            return
        view = navigate_to(self, 'AllOfType')
        provider_entity = view.entities.get_entities_by_keys(
            provider_name=self.ui_name)
        provider_entity[0].check()
        remove_item = 'Remove selected items from Inventory'
        view.toolbar.configuration.item_select(remove_item,
                                               handle_alert=not cancel)
        if not cancel:
            view.flash.assert_success_message(
                'Delete initiated for 1 Provider')
            if wait_deleted:
                wait_for(
                    # check the provider is not listed in all providers anymore
                    func=lambda: not view.entities.get_entities_by_keys(
                        provider_name=self.ui_name),
                    delay=15,
                    fail_func=view.toolbar.refresh.click,
                    num_sec=5 * 60)
                # check the provider is indeed deleted
                assert not self.exists

    @property
    def rest_api_entity(self):
        """Returns the rest entity of config manager provider"""
        # Since config manager provider is slightly different from other normal providers,
        # we cannot obtain it's rest entity the normal way, instead, we use Entity class
        # of manageiq_client library to instantiate the rest entity.
        provider_id = self.appliance.rest_api.collections.providers.get(
            name=self.ui_name).provider_id

        return RestEntity(
            self.appliance.rest_api.collections.providers,
            data={
                "href":
                self.appliance.url_path(
                    "/api/providers/{}?provider_class=provider".format(
                        provider_id))
            })

    # TODO: implement this via Sentaku
    def create_rest(self):
        """Create the config manager in CFME using REST"""
        include_ssl = False
        if self.type == "ansible_tower":
            config_type = "AnsibleTower"
        else:
            config_type = "Foreman"
            include_ssl = True

        payload = {
            "type": "ManageIQ::Providers::{}::Provider".format(config_type),
            "url": self.url,
            "name": self.name,
            "credentials": {
                "userid": self.credentials.view_value_mapping["username"],
                "password": self.credentials.view_value_mapping["password"],
            },
        }

        if include_ssl:
            payload["verify_ssl"] = self.ssl

        try:
            self.appliance.rest_api.post(
                api_endpoint_url=self.appliance.url_path(
                    "/api/providers/?provider_class=provider"),
                **payload)
        except APIException as err:
            raise AssertionError("Provider wasn't added: {}".format(err))

        response = self.appliance.rest_api.response
        if not response:
            raise AssertionError(
                "Provider wasn't added, status code {}".format(
                    response.status_code))

        assert_response(self.appliance)
        return True

    def delete_rest(self):
        """Deletes the config manager from CFME using REST"""
        try:
            self.rest_api_entity.action.delete()
        except APIException as err:
            raise AssertionError("Provider wasn't deleted: {}".format(err))

        response = self.appliance.rest_api.response
        if not response:
            raise AssertionError(
                "Provider wasn't deleted, status code {}".format(
                    response.status_code))

    def refresh_relationships(self, cancel=False):
        """Refreshes relationships and power states of this manager"""
        view = navigate_to(self, 'AllOfType')
        view.toolbar.view_selector.select('List View')
        provider_entity = view.entities.get_entities_by_keys(
            provider_name=self.ui_name)[0]
        provider_entity.check()
        if view.toolbar.configuration.item_enabled(
                'Refresh Relationships and Power states'):
            view.toolbar.configuration.item_select(
                'Refresh Relationships and Power states',
                handle_alert=not cancel)
        if not cancel:
            view.flash.assert_success_message(self.refresh_flash_msg)

    @property
    def config_profiles(self):
        """Returns 'ConfigProfile' configuration profiles (hostgroups) available on this manager"""
        return self.collections.config_profiles.all()

    @property
    def config_systems(self):
        """Returns 'ConfigSystem' configured systems (hosts) available on this manager"""
        systems_per_prof = [
            prof.config_systems for prof in self.config_profiles
        ]
        return [item for sublist in systems_per_prof for item in sublist]

    @property
    def quad_name(self):
        return self.ui_name
Ejemplo n.º 20
0
class ISODatastore(Updateable, Pretty, Navigatable):
    """Model of a PXE Server object in CFME

    Args:
        provider: Provider name.
    """
    _param_name = ParamClassName('ds_name')
    pretty_attrs = ['provider']

    def __init__(self, provider=None, appliance=None):
        Navigatable.__init__(self, appliance=appliance)
        self.provider = provider

    def create(self, cancel=False, refresh=True, refresh_timeout=120):
        """
        Creates an ISO datastore object

        Args:
            cancel (boolean): Whether to cancel out of the creation.  The cancel is done
                after all the information present in the ISO datastore has been filled in the UI.
            refresh (boolean): Whether to run the refresh operation on the ISO datastore after
                the add has been completed.
        """
        view = navigate_to(self, 'Add')
        view.fill({'provider': self.provider})
        main_view = self.create_view(PXEDatastoresView)
        if cancel:
            view.cancel.click()
            msg = 'Add of new ISO Datastore was cancelled by the user'
        else:
            view.add.click()
            msg = 'ISO Datastore "{}" was added'.format(self.provider)
        main_view.flash.assert_success_message(msg)

        if refresh:
            self.refresh(timeout=refresh_timeout)

    @variable(alias='db')
    def exists(self):
        """
        Checks if the ISO Datastore already exists via db
        """
        iso = self.appliance.db.client['iso_datastores']
        ems = self.appliance.db.client['ext_management_systems']
        name = self.provider
        iso_ds = list(self.appliance.db.client.session.query(iso.id)
                      .join(ems, iso.ems_id == ems.id)
                      .filter(ems.name == name))
        if iso_ds:
            return True
        else:
            return False

    @exists.variant('ui')
    def exists_ui(self):
        """
        Checks if the ISO Datastore already exists via UI
        """
        try:
            navigate_to(self, 'Details')
            return True
        except NoSuchElementException:
            return False

    def delete(self, cancel=True):
        """
        Deletes an ISO Datastore from CFME

        Args:
            cancel: Whether to cancel the deletion, defaults to True
        """

        view = navigate_to(self, 'Details')
        msg = 'Remove this ISO Datastore'
        if self.appliance.version >= '5.9':
            msg = 'Remove this ISO Datastore from Inventory'
        view.toolbar.configuration.item_select(msg, handle_alert=not cancel)
        if not cancel:
            main_view = self.create_view(PXEDatastoresView)
            msg = 'ISO Datastore "{}": Delete successful'.format(self.provider)
            main_view.flash.assert_success_message(msg)
        else:
            navigate_to(self, 'Details')

    def refresh(self, wait=True, timeout=120):
        """ Refreshes the PXE relationships and waits for it to be updated
        """
        view = navigate_to(self, 'Details')
        basic_info = view.entities.basic_information
        last_time = basic_info.get_text_of('Last Refreshed On')
        view.toolbar.configuration.item_select('Refresh Relationships', handle_alert=True)
        view.flash.assert_success_message(('ISO Datastore "{}": Refresh Relationships successfully '
                                           'initiated'.format(self.provider)))
        if wait:
            wait_for(lambda lt: lt != basic_info.get_text_of('Last Refreshed On'),
                     func_args=[last_time], fail_func=view.toolbar.reload.click, num_sec=timeout,
                     message="iso refresh")

    def set_iso_image_type(self, image_name, image_type):
        """
        Function to set the image type of a PXE image
        """
        view = navigate_to(self, 'All')
        view.sidebar.datastores.tree.click_path('All ISO Datastores', self.provider,
                                                'ISO Images', image_name)
        view.toolbar.configuration.item_select('Edit this ISO Image')
        view = view.browser.create_view(PXEImageEditView)
        changed = view.fill({'type': image_type})
        # Click save if enabled else click Cancel
        if changed:
            view.save.click()
        else:
            view.cancel.click()
Ejemplo n.º 21
0
class Server(BaseEntity, sentaku.modeling.ElementMixin):
    _param_name = ParamClassName('name')

    sid = attr.ib(default=1)

    address = sentaku.ContextualMethod()
    login = sentaku.ContextualMethod()
    login_admin = sentaku.ContextualMethod()
    logout = sentaku.ContextualMethod()
    update_password = sentaku.ContextualMethod()
    logged_in = sentaku.ContextualMethod()
    current_full_name = sentaku.ContextualMethod()
    current_username = sentaku.ContextualMethod()
    current_group_name = sentaku.ContextualMethod()
    group_names = sentaku.ContextualMethod()
    intel_name = VersionPicker({"5.11": "Overview", Version.lowest(): "Cloud Intel"})

    # zone = sentaku.ContextualProperty()
    # slave_servers = sentaku.ContextualProperty()

    @property
    def name(self):
        """Fetch the name from the master server api entity

        Returns:
            string if entity has the name attribute
            None if its missing
        """
        # empty string default for string building w/o None
        return getattr(self.appliance._rest_api_server, 'name', '')

    @property
    def current_string(self):
        """Returns the string ' (current)' if the appliance serving the UI request
         matches this server instance. Used to generate the appropriate tree_path
         for navigating configuration accordion trees."""
        return ' (current)' if self.sid == self.appliance.server_id() else ''

    @property
    def tree_path(self):
        """Generate the path list for navigation purposes
        list elements follow the tree path in the configuration accordion

        Returns:
            list of path elements for tree navigation
        """
        name_string = f' {self.name} '.replace('  ', ' ')
        path = [
            self.zone.region.settings_string,
            "Zones",
            f"Zone: {self.zone.title_string()}",
            f"Server:{name_string}[{self.sid}]{self.current_string}"  # variables have needed spaces
        ]
        return path

    @property
    def diagnostics_tree_path(self):
        """Generate tree path list for the diagnostics tree in the configuration accordion"""
        path = self.tree_path
        path.remove("Zones")
        return path

    @property
    def settings(self):
        from cfme.configure.configuration.server_settings import ServerInformation
        setting = ServerInformation(appliance=self.appliance)
        return setting

    @property
    def authentication(self):
        from cfme.configure.configuration.server_settings import AuthenticationSetting
        auth_settings = AuthenticationSetting(self.appliance)
        return auth_settings

    @property
    def collect_logs(self):
        from cfme.configure.configuration.diagnostics_settings import ServerCollectLog
        return ServerCollectLog(self.appliance)

    @property
    def zone(self):
        entity = self.rest_api_entity
        entity.reload(attributes=['zone'])
        return self.appliance.collections.zones.instantiate(
            name=entity.zone['name'],
            description=entity.zone['description'],
            id=entity.zone['id']
        )

    @property
    def slave_servers(self):
        return self.zone.collections.servers.filter({'slave': True}).all()

    @property
    def is_slave(self):
        return self in self.slave_servers

    @property
    def secondary_servers(self):
        """ Find and return a list of all other servers in this server's zone. """
        return [s for s in self.zone.collections.servers.all() if s.sid != self.sid]

    @property
    def _api_settings_url(self):
        return '/'.join(
            [self.appliance.rest_api.collections.servers._href, str(self.sid), 'settings']
        )

    @property
    def rest_api_entity(self):
        try:
            return self.appliance.rest_api.collections.servers.get(id=self.sid)
        except ValueError:
            raise RestLookupError(f'No server rest entity found matching ID {self.sid}')

    @property
    def advanced_settings(self):
        """GET servers/:id/settings api endpoint to query server configuration"""
        return self.appliance.rest_api.get(self._api_settings_url)

    def update_advanced_settings(self, settings_dict):
        """PATCH settings from the server's api/server/:id/settings endpoint

        Args:
            settings_dict: dictionary of the changes to be made to the yaml configuration
                       JSON dumps settings_dict to pass as raw hash data to rest_api session
        Raises:
            AssertionError: On an http result >=400 (RequestsResponse.ok)
        """
        # Calling the _session patch method because the core patch will wrap settings_dict in a list
        # Failing with some settings_dict, like 'authentication'
        # https://bugzilla.redhat.com/show_bug.cgi?id=1553394
        result = self.appliance.rest_api._session.patch(
            url=self._api_settings_url,
            data=json.dumps(settings_dict)
        )
        assert result.ok

    def upload_custom_logo(self, file_type, file_data=None, enable=True):
        """
        This function can be used to upload custom logo or text and use them.

        Args:
            file_type (str) : Can be either of [logo, login_logo, brand, favicon, logintext]
            file_data (str) : Text data if file_type is logintext else image path to be uploaded
            enable (bool) : True to use the custom logo/text else False
        """
        view = navigate_to(self, "CustomLogos")
        try:
            logo_view = getattr(view.customlogos, file_type)
        except AttributeError:
            raise AttributeError(
                "File type not in ('logo', 'login_logo', 'brand', 'favicon', 'logintext)."
            )
        if file_data:
            if file_type == "logintext":
                logo_view.fill({"login_text": file_data})
            else:
                logo_view.fill({"image": file_data})
                logo_view.upload_button.click()
            view.flash.assert_no_error()
        logo_view.enable.fill(enable)
        view.customlogos.save_button.click()
        view.flash.assert_no_error()
Ejemplo n.º 22
0
class Server(BaseEntity, sentaku.modeling.ElementMixin):
    _param_name = ParamClassName('name')

    name = attr.ib()
    sid = attr.ib(default=1)

    address = sentaku.ContextualMethod()
    login = sentaku.ContextualMethod()
    login_admin = sentaku.ContextualMethod()
    logout = sentaku.ContextualMethod()
    update_password = sentaku.ContextualMethod()
    logged_in = sentaku.ContextualMethod()
    current_full_name = sentaku.ContextualMethod()
    current_username = sentaku.ContextualMethod()
    current_group_name = sentaku.ContextualMethod()
    group_names = sentaku.ContextualMethod()

    # zone = sentaku.ContextualProperty()
    # slave_servers = sentaku.ContextualProperty()

    @property
    def settings(self):
        from cfme.configure.configuration.server_settings import ServerInformation
        setting = ServerInformation(appliance=self.appliance)
        return setting

    @property
    def authentication(self):
        from cfme.configure.configuration.server_settings import AuthenticationSetting
        auth_settings = AuthenticationSetting(self.appliance)
        return auth_settings

    @property
    def collect_logs(self):
        from cfme.configure.configuration.diagnostics_settings import ServerCollectLog
        return ServerCollectLog(self.appliance)

    @property
    def zone(self):
        server_res = self.appliance.rest_api.collections.servers.find_by(
            id=self.sid)
        server = server_res[0]
        server.reload(attributes=['zone'])
        zone = server.zone
        zone_obj = self.appliance.collections.zones.instantiate(
            name=zone['name'], description=zone['description'], id=zone['id'])
        return zone_obj

    @property
    def slave_servers(self):
        return self.zone.collections.servers.filter({'slave': True}).all()

    @property
    def _api_settings_url(self):
        return '/'.join([
            self.appliance.rest_api.collections.servers._href,
            str(self.sid), 'settings'
        ])

    @property
    def advanced_settings(self):
        """GET servers/:id/settings api endpoint to query server configuration"""
        return self.appliance.rest_api.get(url=self._api_settings_url)

    def update_advanced_settings(self, settings_dict):
        """PATCH settings from the server's api/server/:id/settings endpoint

        Args:
            settings_dict: dictionary of the changes to be made to the yaml configuration
                       JSON dumps settings_dict to pass as raw hash data to rest_api session
        Raises:
            AssertionError: On an http result >=400 (RequestsResponse.ok)
        """
        # Calling the _session patch method because the core patch will wrap settings_dict in a list
        # Failing with some settings_dict, like 'authentication'
        # https://bugzilla.redhat.com/show_bug.cgi?id=1553394
        result = self.appliance.rest_api._session.patch(
            url=self._api_settings_url, data=json.dumps(settings_dict))
        assert result.ok
Ejemplo n.º 23
0
class Datastore(Pretty, BaseEntity, Taggable, CustomButtonEventsMixin):
    """Model of an infrastructure datastore in cfme

    Args:
        name: Name of the datastore.
        provider: provider this datastore is attached to.

    """

    pretty_attrs = ['name', 'provider_key']
    _param_name = ParamClassName('name')
    name = attr.ib()
    provider = attr.ib()
    type = attr.ib(default=None)

    def __attrs_post_init__(self):
        # circular imports
        from cfme.infrastructure.host import HostsCollection
        self._collections = {'hosts': HostsCollection}

    @property
    def rest_api_entity(self):
        return self.appliance.rest_api.collections.data_stores.get(name=self.name)

    def delete(self, cancel=True):
        """
        Deletes a datastore from CFME

        Args:
            cancel: Whether to cancel the deletion, defaults to True

        Note:
            Datastore must have 0 hosts and 0 VMs for this to work.
        """
        # BZ 1467989 - this button is never getting enabled for some resources
        view = navigate_to(self, 'Details')
        view.toolbar.configuration.item_select('Remove Datastore from Inventory'
                                               if self.appliance.version >= '5.9'
                                               else 'Remove Datastore',
                                               handle_alert=(not cancel))
        view.flash.assert_success_message('Delete initiated for Datastore from the CFME Database')

    @property
    def hosts(self):
        return self.collections.hosts

    def get_hosts(self):
        """ Returns names of hosts (from quadicons) that use this datastore

        Returns: List of strings with names or `[]` if no hosts found.
        """
        view = navigate_to(self, 'DetailsFromProvider')
        view.entities.relationships.click_at('Hosts')
        hosts_view = view.browser.create_view(RegisteredHostsView)
        return hosts_view.entities.get_all()

    def get_vms(self):
        """ Returns names of VMs (from quadicons) that use this datastore

        Returns: List of strings with names or `[]` if no vms found.
        """
        view = navigate_to(self, 'Details')
        if 'VMs' in view.entities.relationships.fields:
            view.entities.relationships.click_at('VMs')
        else:
            view.entities.relationships.click_at('Managed VMs')
        # todo: to replace with correct view
        vms_view = view.browser.create_view(DatastoresView)
        return [vm.name for vm in vms_view.entities.get_all()]

    def delete_all_attached_vms(self):
        view = navigate_to(self, 'Details')
        view.entities.relationships.click_at('Managed VMs')
        # todo: to replace with correct view
        vms_view = view.browser.create_view(DatastoresView)
        for entity in vms_view.entities.get_all():
            entity.check()
        view.toolbar.configuration.item_select('Remove selected items from Inventory'
                                               if self.appliance.version >= '5.9'
                                               else 'Remove selected items',
                                               handle_alert=True)

        wait_for(lambda: bool(len(vms_view.entities.get_all())), fail_condition=True,
                 message="Wait datastore vms to disappear", num_sec=1000,
                 fail_func=self.browser.refresh)

    def delete_all_attached_hosts(self):
        view = navigate_to(self, 'Details')
        view.entities.relationships.click_at('Hosts')
        hosts_view = view.browser.create_view(RegisteredHostsView)
        for entity in hosts_view.entities.get_all():
            entity.check()
        view.toolbar.configuration.item_select('Remove items from Inventory'
                                               if self.appliance.version >= '5.9'
                                               else 'Remove items',
                                               handle_alert=True)

        wait_for(lambda: bool(len(hosts_view.entities.get_all())), fail_condition=True,
                 message="Wait datastore hosts to disappear", num_sec=1000,
                 fail_func=self.browser.refresh)

    @property
    def exists(self):
        try:
            view = navigate_to(self, 'Details')
            return view.is_displayed
        except ItemNotFound:
            return False

    @property
    def host_count(self):
        """ number of attached hosts.

        Returns:
            :py:class:`int` host count.
        """
        view = navigate_to(self, 'Details')
        return int(view.entities.relationships.get_text_of('Hosts'))

    @property
    def vm_count(self):
        """ number of attached VMs.

        Returns:
            :py:class:`int` vm count.
        """
        view = navigate_to(self, 'Details')
        return int(view.entities.relationships.get_text_of('Managed VMs'))

    def run_smartstate_analysis(self, wait_for_task_result=False):
        """ Runs smartstate analysis on this host

        Note:
            The host must have valid credentials already set up for this to work.
        """
        view = navigate_to(self, 'DetailsFromProvider')
        try:
            wait_for(lambda: view.toolbar.configuration.item_enabled('Perform SmartState Analysis'),
                     fail_condition=False, num_sec=10)
        except TimedOutError:
            raise MenuItemNotFound('Smart State analysis is disabled for this datastore')
        view.toolbar.configuration.item_select('Perform SmartState Analysis', handle_alert=True)
        view.flash.assert_success_message(('"{}": scan successfully '
                                           'initiated'.format(self.name)))
        if wait_for_task_result:
            task = self.appliance.collections.tasks.instantiate(
                name="SmartState Analysis for [{}]".format(self.name), tab='MyOtherTasks')
            task.wait_for_finished()
            return task

    def wait_candu_data_available(self, timeout=900):
        """Waits until C&U data are available for this Datastore

        Args:
            timeout: Timeout passed to :py:func:`utils.wait.wait_for`
        """
        view = navigate_to(self, 'Details')
        wait_for(
            lambda: view.toolbar.monitoring.item_enabled("Utilization"),
            delay=10, handle_exception=True, num_sec=timeout,
            fail_func=view.browser.refresh
        )
Ejemplo n.º 24
0
class Report(BaseEntity, Updateable):
    _param_name = ParamClassName('title')
    menu_name = attr.ib(default=None)
    title = attr.ib(default=None)
    company_name = attr.ib()
    type = attr.ib(default=None)
    subtype = attr.ib(default=None)
    base_report_on = attr.ib(default=None)
    report_fields = attr.ib(default=None)
    cancel_after = attr.ib(default=None)
    consolidation = attr.ib(default=None)
    formatting = attr.ib(default=None)
    styling = attr.ib(default=None)
    filter = attr.ib(default=None)
    filter_show_costs = attr.ib(default=None)
    filter_owner = attr.ib(default=None)
    filter_tag_cat = attr.ib(default=None)
    filter_tag_value = attr.ib(default=None)
    interval = attr.ib(default=None)
    interval_size = attr.ib(default=None)
    interval_end = attr.ib(default=None)
    sort = attr.ib(default=None)
    chart_type = attr.ib(default=None)
    top_values = attr.ib(default=None)
    sum_other = attr.ib(default=None)
    base_timeline_on = attr.ib(default=None)
    band_units = attr.ib(default=None)
    event_position = attr.ib(default=None)
    show_event_unit = attr.ib(default=None)
    show_event_count = attr.ib(default=None)
    summary = attr.ib(default=None)
    charts = attr.ib(default=None)
    timeline = attr.ib(default=None)
    is_candu = attr.ib(default=False)

    def __attrs_post_init__(self):
        self._collections = {'saved_reports': SavedReportsCollection}

    @company_name.default
    def company_name_default(self):
        if self.appliance.version < "5.9":
            return "My Company (All EVM Groups)"
        else:
            return "My Company (All Groups)"

    def update(self, updates):
        view = navigate_to(self, "Edit")
        changed = view.fill(updates)
        if changed:
            view.save_button.click()
        else:
            view.cancel_button.click()
        view = self.create_view(ReportDetailsView, override=updates)
        assert view.is_displayed
        view.flash.assert_no_error()
        if changed:
            view.flash.assert_message('Report "{}" was saved'.format(
                self.menu_name))
        else:
            view.flash.assert_message(
                'Edit of Report "{}" was cancelled by the user'.format(
                    self.menu_name))

    def delete(self, cancel=False):
        view = navigate_to(self, "Details")
        node = view.reports.tree.expand_path("All Reports", self.company_name,
                                             "Custom")
        custom_reports_number = len(view.reports.tree.child_items(node))
        view.configuration.item_select("Delete this Report from the Database",
                                       handle_alert=not cancel)
        if cancel:
            assert view.is_displayed
            view.flash.assert_no_error()
        else:
            # This check is needed because after deleting the last custom report,
            # the whole "My Company (All EVM Groups)" branch in the tree will be removed.
            if custom_reports_number > 1:
                view = self.create_view(AllCustomReportsView)
                assert view.is_displayed
            view.flash.assert_no_error()
            if not BZ(1561779, forced_streams=['5.9', '5.8']).blocks:
                view.flash.assert_message(
                    'Report "{}": Delete successful'.format(self.menu_name))

    @cached_property
    def saved_reports(self):
        return self.collections.saved_reports

    def queue(self, wait_for_finish=False):
        view = navigate_to(self, "Details")
        view.report_info.queue_button.click()
        view.flash.assert_no_error()
        if wait_for_finish:
            # Get the queued_at value to always target the correct row
            queued_at = view.saved_reports.table[0]["Queued At"].text

            def _get_state():
                row = view.saved_reports.table.row(queued_at=queued_at)
                status = row.status.text.strip().lower()
                assert status != "error"
                return status == "complete"

            wait_for(
                _get_state,
                delay=1,
                message="wait for report generation finished",
                fail_func=view.reload_button.click,
                num_sec=300,
            )
            view.reload_button.click()
        first_row = view.saved_reports.table[0]
        saved_report = self.saved_reports.instantiate(first_row.run_at.text,
                                                      first_row.queued_at.text,
                                                      self.is_candu)
        return saved_report

    @property
    def exists(self):
        try:
            navigate_to(self, "Details")
            return True
        except CandidateNotFound:
            return False
Ejemplo n.º 25
0
class CustomReport(Updateable, Navigatable):
    _param_name = ParamClassName('title')
    _default_dict = {
        "menu_name": None,
        "title": None,
        "base_report_on": None,
        "report_fields": None,
        "cancel_after": None,
        "consolidation": None,
        "formatting": None,
        "styling": None,
        "filter": None,
        "filter_show_costs": None,
        "filter_owner": None,
        "filter_tag_cat": None,
        "filter_tag_value": None,
        "interval": None,
        "interval_size": None,
        "interval_end": None,
        "sort": None,
        "chart_type": None,
        "top_values": None,
        "sum_other": None,
        "base_timeline_on": None,
        "band_units": None,
        "event_position": None,
        "show_event_unit": None,
        "show_event_count": None,
        "summary": None,
        "charts": None,
        "timeline": None
    }

    def __init__(self, appliance=None, **values):
        Navigatable.__init__(self, appliance=appliance)
        # We will override the original dict
        self.__dict__ = dict(self._default_dict)
        self.__dict__.update(values)
        # We need to pass the knowledge whether it is a candu report
        try:
            self.is_candu
        except AttributeError:
            self.is_candu = False

    def create(self, cancel=False):
        view = navigate_to(self, "Add")
        view.fill(self.__dict__)
        view.add_button.click()
        view = self.create_view(AllReportsView)
        assert view.is_displayed
        view.flash.assert_no_error()
        view.flash.assert_message('Report "{}" was added'.format(
            self.menu_name))

    def update(self, updates):
        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(CustomReportDetailsView)
        assert view.is_displayed
        view.flash.assert_no_error()
        if changed:
            view.flash.assert_message('Report "{}" was saved'.format(
                self.menu_name))
        else:
            view.flash.assert_message(
                'Edit of Report "{}" was cancelled by the user'.format(
                    self.menu_name))

    def delete(self, cancel=False):
        view = navigate_to(self, "Details")
        node = view.reports.tree.expand_path("All Reports",
                                             view.mycompany_title, "Custom")
        custom_reports_number = len(view.reports.tree.child_items(node))
        view.configuration.item_select("Delete this Report from the Database",
                                       handle_alert=not cancel)
        if cancel:
            assert view.is_displayed
            view.flash.assert_no_error()
        else:
            # This check is needed because after deleting the last custom report,
            # the whole "My Company (All EVM Groups)" branch in the tree will be removed.
            if custom_reports_number > 1:
                view = self.create_view(AllCustomReportsView)
                assert view.is_displayed
            view.flash.assert_no_error()
            if not BZ(1561779, forced_streams=['5.9']).blocks:
                view.flash.assert_message(
                    'Report "{}": Delete successful'.format(self.menu_name))

    def get_saved_reports(self):
        view = navigate_to(self, "Details")
        results = []
        try:
            for _ in view.saved_reports.paginator.pages():
                for row in view.saved_reports.table.rows():
                    results.append(
                        CustomSavedReport(self,
                                          row.run_at.text.encode("utf-8"),
                                          row.queued_at.text.encode("utf-8"),
                                          self.is_candu))
        except NoSuchElementException:
            pass
        return results

    def queue(self, wait_for_finish=False):
        view = navigate_to(self, "Details")
        view.report_info.queue_button.click()
        view.flash.assert_no_error()
        if wait_for_finish:
            # Get the queued_at value to always target the correct row
            queued_at = view.saved_reports.table[0]["Queued At"].text

            def _get_state():
                row = view.saved_reports.table.row(queued_at=queued_at)
                status = row.status.text.strip().lower()
                assert status != "error"
                return status == "complete"

            wait_for(
                _get_state,
                delay=1,
                message="wait for report generation finished",
                fail_func=view.reload_button.click,
                num_sec=300,
            )

    @property
    def exists(self):
        try:
            navigate_to(self, "Details")
            return True
        except CandidateNotFound:
            return False
Ejemplo n.º 26
0
class Datastore(Pretty, BaseEntity, WidgetasticTaggable):
    """ Model of an infrastructure datastore in cfme

    Args:
        name: Name of the datastore.
        provider: provider this datastore is attached to.
    """

    pretty_attrs = ['name', 'provider_key']
    _param_name = ParamClassName('name')

    name = attr.ib()
    provider = attr.ib()
    type = attr.ib(default=None)

    def delete(self, cancel=True):
        """
        Deletes a datastore from CFME

        Args:
            cancel: Whether to cancel the deletion, defaults to True

        Note:
            Datastore must have 0 hosts and 0 VMs for this to work.
        """
        # BZ 1467989 - this button is never getting enabled for some resources
        view = navigate_to(self, 'Details')
        view.toolbar.configuration.item_select(VersionPick({
            Version.lowest():
            'Remove Datastore',
            '5.9':
            'Remove Datastore from Inventory'
        }),
                                               handle_alert=(not cancel))
        view.flash.assert_success_message(
            'Delete initiated for Datastore from the CFME Database')

    def get_hosts(self):
        """ Returns names of hosts (from quadicons) that use this datastore

        Returns: List of strings with names or `[]` if no hosts found.
        """
        view = navigate_to(self, 'DetailsFromProvider')
        view.entities.relationships.click_at('Hosts')
        hosts_view = view.browser.create_view(RegisteredHostsView)
        return hosts_view.entities.get_all()

    def get_vms(self):
        """ Returns names of VMs (from quadicons) that use this datastore

        Returns: List of strings with names or `[]` if no vms found.
        """
        view = navigate_to(self, 'Details')
        if 'VMs' in view.entities.relationships.fields:
            view.entities.relationships.click_at('VMs')
        else:
            view.entities.relationships.click_at('Managed VMs')
        # todo: to replace with correct view
        vms_view = view.browser.create_view(DatastoresView)
        return [vm.name for vm in vms_view.entities.get_all()]

    def delete_all_attached_vms(self):
        view = navigate_to(self, 'Details')
        view.entities.relationships.click_at('Managed VMs')
        # todo: to replace with correct view
        vms_view = view.browser.create_view(DatastoresView)
        for entity in vms_view.entities.get_all():
            entity.check()
        view.toolbar.configuration.item_select(VersionPick({
            Version.lowest():
            'Remove selected items',
            '5.9':
            'Remove selected items from Inventory'
        }),
                                               handle_alert=True)
        wait_for(lambda: bool(len(vms_view.entities.get_all())),
                 fail_condition=True,
                 message="Wait datastore vms to disappear",
                 num_sec=1000,
                 fail_func=self.browser.refresh)

    def delete_all_attached_hosts(self):
        view = navigate_to(self, 'Details')
        view.entities.relationships.click_at('Hosts')
        hosts_view = view.browser.create_view(RegisteredHostsView)
        for entity in hosts_view.entities.get_all():
            entity.check()
        view.toolbar.configuration.item_select(VersionPick({
            Version.lowest():
            'Remove items',
            '5.9':
            'Remove items from Inventory'
        }),
                                               handle_alert=True)
        wait_for(lambda: bool(len(hosts_view.entities.get_all())),
                 fail_condition=True,
                 message="Wait datastore hosts to disappear",
                 num_sec=1000,
                 fail_func=self.browser.refresh)

    @property
    def exists(self):
        try:
            view = navigate_to(self, 'Details')
            return view.is_displayed
        except ItemNotFound:
            return False

    def run_smartstate_analysis(self, wait_for_task_result=False):
        """ Runs smartstate analysis on this host

        Note:
            The host must have valid credentials already set up for this to work.
        """
        view = navigate_to(self, 'DetailsFromProvider')
        try:
            wait_for(lambda: view.toolbar.configuration.item_enabled(
                'Perform SmartState Analysis'),
                     fail_condition=False,
                     num_sec=10)
        except TimedOutError:
            raise MenuItemNotFound(
                'Smart State analysis is disabled for this datastore')
        view.toolbar.configuration.item_select('Perform SmartState Analysis',
                                               handle_alert=True)
        view.flash.assert_success_message(('"{}": scan successfully '
                                           'initiated'.format(self.name)))
        if wait_for_task_result:
            view = self.appliance.browser.create_view(TasksView)
            wait_for(lambda: is_datastore_analysis_finished(self.name),
                     delay=15,
                     timeout="15m",
                     fail_func=view.reload.click)
Ejemplo n.º 27
0
class ComputeRate(Updateable, Pretty, Navigatable):
    """This class represents a Compute Chargeback rate.

    Example:
        .. code-block:: python

          >>> import cfme.intelligence.chargeback.rates as rates
          >>> rate = rates.ComputeRate(description=desc,
                    fields={'Used CPU':
                            {'per_time': 'Hourly', 'variable_rate': '3'},
                            'Used Disk I/O':
                            {'per_time': 'Hourly', 'variable_rate': '2'},
                            'Used Memory':
                            {'per_time': 'Hourly', 'variable_rate': '2'}})
          >>> rate.create()
          >>> rate.delete()

    Args:
        description: Rate description
        currency: Rate currency
        fields  : Rate fields
    """

    pretty_attrs = ['description']
    _param_name = ParamClassName('description')
    RATE_TYPE = 'Compute'

    def __init__(
        self,
        description=None,
        currency=None,
        fields=None,
        appliance=None,
    ):
        Navigatable.__init__(self, appliance=appliance)
        self.description = description
        self.currency = currency
        self.fields = fields

    def __getitem__(self, name):
        return self.fields.get(name)

    @property
    def exists(self):
        try:
            navigate_to(self, 'Details', wait_for_view=True)
        except (ChargebackRateNotFound, TimedOutError):
            return False
        else:
            return True

    def create(self):
        # Create a rate in UI
        view = navigate_to(self, 'Add')
        view.fill_with(
            {
                'description': self.description,
                'currency': self.currency,
                'fields': self.fields
            },
            on_change=view.add_button,
            no_change=view.cancel_button)

        view = self.create_view(navigator.get_class(self, 'All').VIEW)
        assert view.is_displayed
        view.flash.assert_no_error()

    def copy(self, *args, **kwargs):
        new_rate = ComputeRate(*args, **kwargs)
        add_view = navigate_to(self, 'Copy')
        add_view.fill_with(
            {
                'description': new_rate.description,
                'currency': new_rate.currency,
                'fields': new_rate.fields
            },
            on_change=add_view.add_button,
            no_change=add_view.cancel_button)

        return new_rate

    def update(self, updates):
        # Update a rate in UI
        view = navigate_to(self, 'Edit')
        view.fill_with(updates,
                       on_change=view.save_button,
                       no_change=view.cancel_button)

        view = self.create_view(navigator.get_class(self, 'Details').VIEW)
        view.flash.assert_no_error()

    def delete(self, cancel=False):
        """Delete a CB rate in the UI
        Args:
            cancel: boolean, whether to cancel the action on alert
        """
        view = navigate_to(self, 'Details', wait_for_view=True)
        view.toolbar.configuration.item_select('Remove from the VMDB',
                                               handle_alert=(not cancel))
        view = self.create_view(navigator.get_class(self, 'All').VIEW)
        assert view.is_displayed
        view.flash.assert_no_error()
Ejemplo n.º 28
0
class BaseAlertProfile(BaseEntity, Updateable, Pretty):

    TYPE = None
    _param_name = ParamClassName('description')
    pretty_attrs = ["description", "alerts"]

    description = attr.ib()
    alerts = attr.ib(default=None)
    notes = attr.ib(default=None)

    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 attrib, value in updates.items():
            setattr(self, attrib, value)
        view = self.create_view(AlertProfileDetailsView)
        wait_for(lambda: view.is_displayed,
                 timeout=10,
                 message="wait AlertProfileDetailsView 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()

    @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.

        Returns:
            Boolean indicating if assignment was made (form fill changed)
        """
        view = navigate_to(self, "Edit assignments")
        changed = []
        if selections is not None:
            selections = view.selections.CheckNode(selections)
        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()
        return changed
Ejemplo n.º 29
0
class BaseProvider(Taggable, Updateable, Navigatable, BaseEntity):
    # List of constants that every non-abstract subclass must have defined

    # TODO: Navigatable is used to ensure function until the reduced get_crud is
    # replaced by methods on collections. This will be fixed in next conversion PR

    _param_name = ParamClassName('name')
    STATS_TO_MATCH = []
    db_types = ["Providers"]
    ems_events = []
    settings_key = None
    vm_class = None  # Set on type specific provider classes for VM/instance class
    template_class = None  # Set on type specific provider classes for VM template class

    endpoints = attr.ib(default=attr.Factory(factory=dict))

    def __attrs_post_init__(self):
        # attr.ib(convert=prepare_endpoints) doesn't work correctly. this is workaround
        self.endpoints = prepare_endpoints(self.endpoints)

    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 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))

    @property
    def mgmt(self):
        """ Returns the mgmt_system using the :py:func:`utils.providers.get_mgmt` method.
        """
        # gotta stash this in here to prevent circular imports
        from cfme.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))

    @property
    def type(self):
        return self.type_name

    @property
    def rest_api_entity(self):
        return self.appliance.rest_api.collections.providers.get(
            name=self.name)

    @property
    def id(self):
        """"
        Return the ID associated with the specified provider name
        """
        return self.rest_api_entity.id

    @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 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

        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)
            add_view = navigate_to(self, 'Add')

            if not cancel or (cancel
                              and any(self.view_value_mapping.values())):
                # Workaround for BZ#1526050
                from cfme.infrastructure.provider.virtualcenter import VMwareProvider
                if self.appliance.version == '5.8.3.0' and self.one_of(
                        VMwareProvider):
                    add_view.fill({'prov_type': 'Red Hat Virtualization'})
                elif '5.8.3.0' < self.appliance.version < '5.9':
                    import warnings
                    warnings.warn('REMOVE ME: BZ#1526050')
                # 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)
                    # sometimes we have cases that we need to validate even though
                    # there is no credentials, such as Hawkular endpoint
                    if (validate_credentials
                            and hasattr(endp_view, 'validate')
                            and endp_view.validate.is_displayed):
                        # there are some endpoints which don't demand validation like
                        #  RSA key pair
                        endp_view.validate.click()
                        # Flash message widget is in add_view, not in endpoints tab
                        logger.info(
                            'Validating credentials flash message for endpoint %s',
                            endpoint_name)
                        add_view.flash.assert_no_error()
                        add_view.flash.assert_success_message(
                            'Credential validation was successful')

            main_view = self.create_view(navigator.get_class(self, 'All').VIEW)
            if cancel:
                created = False
                add_view.cancel.click()
                cancel_text = ('Add of {} Provider was '
                               'cancelled by the user'.format(
                                   self.string_name))

                main_view.flash.assert_message(cancel_text)
                main_view.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.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

    def _fill_provider_attributes(self, provider_attributes):
        """Fills provider data.

        Helper method for ``self.create_rest``
        """
        if getattr(self, "region", None):
            if isinstance(self.region, dict):
                provider_attributes["provider_region"] = VersionPicker(
                    self.region).pick(self.appliance.version)
            else:
                provider_attributes["provider_region"] = self.region
        if getattr(self, "project", None):
            provider_attributes["project"] = self.project

        if self.type_name in ('openstack_infra', 'openstack'):
            if getattr(self, 'api_version', None):
                version = 'v3' if 'v3' in self.api_version else 'v2'
                provider_attributes['api_version'] = version
                if version == 'v3' and getattr(self, 'keystone_v3_domain_id',
                                               None):
                    provider_attributes['uid_ems'] = self.keystone_v3_domain_id

        if self.type_name == "azure":
            provider_attributes["uid_ems"] = self.tenant_id
            provider_attributes["provider_region"] = self.region.lower(
            ).replace(" ", "")
            if getattr(self, "subscription_id", None):
                provider_attributes["subscription"] = self.subscription_id

    def _fill_default_endpoint_dicts(self, provider_attributes,
                                     connection_configs):
        """Fills dicts with default endpoint data.

        Helper method for ``self.create_rest``
        """
        default_connection = {"endpoint": {"role": "default"}}

        endpoint_default = self.endpoints["default"]
        if getattr(endpoint_default.credentials, "principal", None):
            provider_attributes["credentials"] = {
                "userid": endpoint_default.credentials.principal,
                "password": endpoint_default.credentials.secret,
            }
        elif getattr(endpoint_default.credentials, "service_account", None):
            default_connection["authentication"] = {
                "type": "AuthToken",
                "auth_type": "default",
                "auth_key": endpoint_default.credentials.service_account,
            }
            connection_configs.append(default_connection)
        else:
            raise AssertionError(
                "Provider wasn't added. "
                "No credentials info found for provider {}.".format(self.name))

        cert = getattr(endpoint_default, "ca_certs", None)
        if cert and self.appliance.version >= "5.8":
            default_connection["endpoint"]["certificate_authority"] = cert
            connection_configs.append(default_connection)

        if hasattr(endpoint_default, "verify_tls"):
            default_connection["endpoint"][
                "verify_ssl"] = 1 if endpoint_default.verify_tls else 0
            connection_configs.append(default_connection)
        if getattr(endpoint_default, "api_port", None):
            default_connection["endpoint"]["port"] = endpoint_default.api_port
            connection_configs.append(default_connection)
        if getattr(endpoint_default, "security_protocol", None):
            security_protocol = endpoint_default.security_protocol.lower()
            if security_protocol in ('basic (ssl)', 'ssl without validation'):
                security_protocol = "ssl"
            elif security_protocol == "ssl":
                security_protocol = 'ssl-with-validation'

            default_connection["endpoint"][
                "security_protocol"] = security_protocol
            connection_configs.append(default_connection)

    def _fill_candu_endpoint_dicts(self, provider_attributes,
                                   connection_configs):
        """Fills dicts with candu endpoint data.

        Helper method for ``self.create_rest``
        """
        if "candu" not in self.endpoints:
            return

        endpoint_candu = self.endpoints["candu"]
        if isinstance(provider_attributes["credentials"], dict):
            provider_attributes["credentials"] = [
                provider_attributes["credentials"]
            ]
        provider_attributes["credentials"].append({
            "userid":
            endpoint_candu.credentials.principal,
            "password":
            endpoint_candu.credentials.secret,
            "auth_type":
            "metrics",
        })
        candu_connection = {
            "endpoint": {
                "hostname": endpoint_candu.hostname,
                "path": endpoint_candu.database,
                "role": "metrics",
            },
        }
        if getattr(endpoint_candu, "api_port", None):
            candu_connection["endpoint"]["port"] = endpoint_candu.api_port
        if hasattr(endpoint_candu,
                   "verify_tls") and not endpoint_candu.verify_tls:
            candu_connection["endpoint"]["verify_ssl"] = 0
        connection_configs.append(candu_connection)

    def _fill_rsa_endpoint_dicts(self, provider_attributes,
                                 connection_configs):
        """Fills dicts with rsa endpoint data.

        Helper method for ``self.create_rest``
        """
        if "rsa_keypair" not in self.endpoints:
            return

        endpoint_rsa = self.endpoints["rsa_keypair"]
        if isinstance(provider_attributes["credentials"], dict):
            provider_attributes["credentials"] = [
                provider_attributes["credentials"]
            ]

        provider_attributes["credentials"].append({
            "userid":
            endpoint_rsa.credentials.principal,
            "auth_key":
            endpoint_rsa.credentials.secret,
            "auth_type":
            "ssh_keypair",
        })

    def _compile_connection_configurations(self, provider_attributes,
                                           connection_configs):
        """Compiles togetger all dicts with data for ``connection_configurations``.

        Helper method for ``self.create_rest``
        """
        provider_attributes["connection_configurations"] = []
        appended = []
        for config in connection_configs:
            role = config["endpoint"]["role"]
            if role not in appended:
                provider_attributes["connection_configurations"].append(config)
                appended.append(role)

    def create_rest(self, check_existing=False, validate_inventory=False):
        """
        Creates a provider using REST

        Args:
            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

        Returns:
            True if it was created, False if it already existed
        """
        if check_existing and self.exists:
            return False

        logger.info("Setting up provider via REST: %s", self.key)

        # provider attributes
        provider_attributes = {
            "hostname": self.hostname,
            "ipaddress": self.ip_address,
            "name": self.name,
            "type": "ManageIQ::Providers::{}".format(self.db_types[0]),
        }

        # data for provider_attributes['connection_configurations']
        connection_configs = []

        # produce final provider_attributes
        self._fill_provider_attributes(provider_attributes)
        self._fill_default_endpoint_dicts(provider_attributes,
                                          connection_configs)
        self._fill_candu_endpoint_dicts(provider_attributes,
                                        connection_configs)
        self._fill_rsa_endpoint_dicts(provider_attributes, connection_configs)
        self._compile_connection_configurations(provider_attributes,
                                                connection_configs)

        try:
            self.appliance.rest_api.collections.providers.action.create(
                **provider_attributes)
        except APIException as err:
            raise AssertionError("Provider wasn't added: {}".format(err))

        response = self.appliance.rest_api.response
        if not response:
            raise AssertionError(
                "Provider wasn't added, status code {}".format(
                    response.status_code))

        if validate_inventory:
            self.validate()

        self.appliance.rest_api.response = response
        return True

    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
        """
        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 = 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)
                # sometimes we have cases that we need to validate even though
                # there is no credentials, such as Hawkular endpoint
                if (validate_credentials and hasattr(endp_view, 'validate')
                        and endp_view.validate.is_displayed):
                    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)
                if hasattr(endp_view,
                           'validate') and endp_view.validate.is_displayed:
                    endp_view.validate.click()

        details_view = self.create_view(
            navigator.get_class(self, 'Details').VIEW)
        main_view = self.create_view(navigator.get_class(self, 'All').VIEW)

        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.flash.assert_message(cancel_text)
            main_view.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)

            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")

    def delete(self, cancel=True):
        """
        Deletes a provider from CFME using UI

        Args:
            cancel: Whether to cancel the deletion, defaults to True
        """
        view = navigate_to(self, 'Details')
        item_title = 'Remove this {} Provider from Inventory'
        view.toolbar.configuration.item_select(item_title.format(
            self.string_name),
                                               handle_alert=not cancel)
        if not cancel:
            msg = ('Delete initiated for 1 {} Provider from '
                   'the {} Database'.format(self.string_name,
                                            self.appliance.product_name))
            view.flash.assert_success_message(msg)

    def delete_rest(self):
        """Deletes a provider from CFME using REST"""
        provider_rest = self.appliance.rest_api.collections.providers.get(
            name=self.name)

        try:
            provider_rest.action.delete()
        except APIException as err:
            raise AssertionError("Provider wasn't deleted: {}".format(err))

        response = self.appliance.rest_api.response
        if not response:
            raise AssertionError(
                "Provider wasn't deleted, status code {}".format(
                    response.status_code))

    def setup(self):
        """
        Sets up the provider robustly
        """
        # TODO: Eventually this will become Sentakuified, but only after providers is CEMv3
        if self.category in ['cloud', 'infra', 'physical']:
            return self.create_rest(check_existing=True,
                                    validate_inventory=True)
        else:
            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, refresh_delta=600):
        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, refresh_delta):
            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
        else:
            if self.last_refresh_error() is not None:
                raise AddProviderError(
                    "Cannot validate the provider. Error occured: {}".format(
                        self.last_refresh_error()))

    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:
            view = navigate_to(self, 'All')
            entity = view.entities.get_entity(name=self.name, surf_pages=True)
            entity.check()

        else:
            view = navigate_to(self, 'Details')

        view.toolbar.configuration.item_select(self.refresh_text,
                                               handle_alert=True)

    @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

    @variable(alias='rest')
    def last_refresh_error(self):
        try:
            col = self.appliance.rest_api.collections.providers.find_by(
                name=self.name)[0]
            return col.last_refresh_error
        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:
            self.browser.selenium.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):
        try:
            provider_rest = self.appliance.rest_api.collections.providers.get(
                name=self.name)
        except (ValueError, APIException
                ):  # if the record doesn't exist, APIException from 404
            return

        logger.info('Waiting for a provider to delete...')
        provider_rest.wait_not_exists(message="Wait provider to disappear",
                                      num_sec=1000)

    def load_details(self, refresh=False):
        """To be compatible with the Taggable and PolicyProfileAssignable mixins.

        Returns: ProviderDetails view
        """
        view = navigate_to(self, 'Details')
        if refresh:
            view.toolbar.reload.click()
        return view

    @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 cfme.utils.appliance import current_appliance as app
        # Delete all matching
        for prov in app.managed_known_providers:
            if prov.one_of(cls):
                logger.info('Deleting provider: %s', prov.name)
                prov.delete_rest()
        # Wait for all matching to be deleted
        for prov in app.managed_known_providers:
            if prov.one_of(cls):
                prov.wait_for_delete()

    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)

    # These methods need to be overridden in the provider specific classes
    def get_console_connection_status(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_remote_console_canvas(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_console_ctrl_alt_del_btn(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_console_fullscreen_btn(self):
        raise NotImplementedError(
            "This method is not implemented for given provider")

    def get_all_provider_ids(self):
        """
        Returns an integer list of provider ID's via the REST API
        """
        # TODO: Move to ProviderCollection
        logger.debug('Retrieving the list of provider ids')

        provider_ids = []
        try:
            for prov in self.appliance.rest_api.collections.providers.all:
                provider_ids.append(prov.id)
        except APIException:
            return None

        return provider_ids

    def get_all_vm_ids(self):
        """
        Returns an integer list of vm ID's via the REST API
        """
        # TODO: Move to VMCollection or BaseVMCollection
        logger.debug('Retrieving the list of vm ids')

        vm_ids = []
        try:
            for vm in self.appliance.rest_api.collections.vms.all:
                vm_ids.append(vm.id)
        except APIException:
            return None

        return vm_ids

    def get_all_host_ids(self):
        """
        Returns an integer list of host ID's via the Rest API
        """
        # TODO: Move to HostCollection
        logger.debug('Retrieving the list of host ids')

        host_ids = []
        try:
            for host in self.appliance.rest_api.collections.hosts.all:
                host_ids.append(host.id)
        except APIException:
            return None
        return host_ids

    def get_all_template_ids(self):
        """Returns an integer list of template ID's via the Rest API"""
        # TODO: Move to TemplateCollection
        logger.debug('Retrieving the list of template ids')

        template_ids = []
        try:
            for template in self.appliance.rest_api.collections.templates.all:
                template_ids.append(template.id)
        except APIException:
            return None
        return template_ids

    def get_provider_details(self, provider_id):
        """Returns the name, and type associated with the provider_id"""
        # TODO: Move to ProviderCollection.find
        logger.debug(
            'Retrieving the provider details for ID: {}'.format(provider_id))

        details = {}
        try:
            prov = self.appliance.rest_api.collections.providers.get(
                id=provider_id)
        except APIException:
            return None
        details['id'] = prov.id
        details['name'] = prov.name
        details['type'] = prov.type

        return details

    def get_vm_details(self, vm_id):
        """
        Returns the name, type, vendor, host_id, and power_state associated with
        the vm_id.
        """
        # TODO: Move to VMCollection.find
        logger.debug('Retrieving the VM details for ID: {}'.format(vm_id))

        details = {}
        try:
            vm = self.appliance.rest_api.collections.vms.get(id=vm_id)
        except APIException:
            return None

        details['id'] = vm.id
        details['ems_id'] = vm.ems_id
        details['name'] = vm.name
        details['type'] = vm.type
        details['vendor'] = vm.vendore
        details['host_id'] = vm.host_id
        details['power_state'] = vm.power_state
        return details

    def get_template_details(self, template_id):
        """
        Returns the name, type, and guid associated with the template_id
        """
        # TODO: Move to TemplateCollection.find
        logger.debug(
            'Retrieving the template details for ID: {}'.format(template_id))

        template_details = {}
        try:
            template = self.appliance.rest_api.collections.templates.get(
                id=template_id)
        except APIException:
            return None

        template_details['name'] = template.name
        template_details['type'] = template.type
        template_details['guid'] = template.guid
        return template_details

    def get_all_template_details(self):
        """
        Returns a dictionary mapping template ids to their name, type, and guid
        """
        # TODO: Move to TemplateCollection.all
        all_details = {}
        for id in self.get_all_template_ids():
            all_details[id] = self.get_template_details(id)
        return all_details

    def get_vm_id(self, vm_name):
        """
        Return the ID associated with the specified VM name
        """
        # TODO: Get Provider object from VMCollection.find, then use VM.id to get the id
        logger.debug('Retrieving the ID for VM: {}'.format(vm_name))
        for vm_id in self.get_all_vm_ids():
            details = self.get_vm_details(vm_id)
            if details['name'] == vm_name:
                return vm_id

    def get_vm_ids(self, vm_names):
        """
        Returns a dictionary mapping each VM name to it's id
        """
        # TODO: Move to VMCollection.find or VMCollection.all
        name_list = vm_names[:]
        logger.debug('Retrieving the IDs for {} VM(s)'.format(len(name_list)))
        id_map = {}
        for vm_id in self.get_all_vm_ids():
            if not name_list:
                break
            vm_name = self.get_vm_details(vm_id)['name']
            if vm_name in name_list:
                id_map[vm_name] = vm_id
                name_list.remove(vm_name)
        return id_map

    def get_template_guids(self, template_dict):
        """
        Returns a list of tuples. The inner tuples are formated so that each guid
        is in index 0, and its provider's name is in index 1. Expects a dictionary
        mapping a provider to its templates
        """
        # TODO: Move to TemplateCollection
        result_list = []
        all_template_details = self.get_all_template_details()
        for provider, templates in template_dict.items():
            for template_name in templates:
                inner_tuple = ()
                for id in all_template_details:
                    if ((all_template_details[id]['name'] == template_name)
                            and (self.db_types[0]
                                 in all_template_details[id]['type'])):
                        inner_tuple += (all_template_details[id]['guid'], )
                        inner_tuple += (provider, )
                        result_list.append(inner_tuple)
        return result_list