def create(self, name, storage_manager, tenant, size, provider):
        """Create new storage volume

        Args:
            name: volume name
            storage_manager: storage manager name
            tenant: tenant name
            size: volume size in GB
            provider: provider

        Returns:
            object for the :py:class: cfme.storage.volume.Volume
        """

        view = navigate_to(self, 'Add')
        view.form.fill({
            'storage_manager': storage_manager,
            'tenant': tenant,
            'volume_name': name,
            'size': size
        })
        view.form.add.click()
        base_message = VersionPick({
            Version.lowest(): 'Creating Cloud Volume "{}"',
            '5.8': 'Cloud Volume "{}" created'
        }).pick(self.appliance.version)
        view.flash.assert_success_message(base_message.format(name))

        volume = self.instantiate(name, provider)
        wait_for(lambda: volume.exists,
                 delay=20,
                 timeout=500,
                 fail_func=volume.refresh)

        return volume
class ConfigManagementAddForm(View):
    """Form to add a provider"""
    name = TextInput('name')
    provider_type = BootstrapSelect('provider_type')
    zone = TextInput('zone')
    url = TextInput('url')
    ssl = Checkbox('verify_ssl')

    username = VersionPick({Version.lowest(): TextInput('log_userid'),
                            '5.9': TextInput('default_userid')})
    password = VersionPick({Version.lowest(): TextInput('log_password'),
                          '5.9': TextInput('default_password')})
    confirm_password = TextInput('log_verify')

    validate = Button('Validate')
    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)
Exemple #4
0
 class purpose(Tab):  # noqa
     TAB_NAME = 'Purpose'
     apply_tags = VersionPick({
         Version.lowest():
         CheckboxSelect('//div[@id="all_tags_treebox"]//ul'),
         '5.7':
         BootstrapTreeview('all_tags_treebox')
     })
Exemple #5
0
def InstanceEntity():  # noqa
    """ Temporary wrapper for Instance Entity during transition to JS based Entity

    """
    return VersionPick({
        Version.lowest(): NonJSInstanceEntity,
        '5.9': JSInstanceEntity,
    })
Exemple #6
0
 class properties(Tab):  # noqa
     TAB_NAME = 'Properties'
     instance_type = BootstrapSelect('hardware__instance_type')
     guest_keypair = BootstrapSelect('hardware__guest_access_key_pair')
     hardware_monitoring = BootstrapSelect('hardware__monitoring')
     boot_disk_size = BootstrapSelect('hardware__boot_disk_size')
     # GCE
     is_preemtible = VersionPick({
         Version.lowest(): None, '5.7': Input(name='hardware__is_preemptible')})
class ConfigManagementDetailsToolbar(View):
    """Toolbar on the details page"""
    history = Dropdown(title='History')
    refresh = Button(title=VersionPick({Version.lowest(): 'Reload current display',
                                       '5.9': 'Refresh this page'}))
    lifecycle = Dropdown('Lifecycle')
    policy = Dropdown('Policy')
    download = Button(title='Download summary in PDF format')
    view_selector = View.nested(ItemsToolBarViewSelector)
class ConfigManagementToolbar(View):
    """Toolbar"""
    refresh = Button(title=VersionPick({Version.lowest(): 'Reload current display',
                                       '5.9': 'Refresh this page'}))
    configuration = Dropdown('Configuration')
    lifecycle = Dropdown('Lifecycle')
    policy = Dropdown('Policy')
    download = Dropdown(title='Download')
    view_selector = View.nested(ItemsToolBarViewSelector)
Exemple #9
0
class Volume(NavigatableMixin):
    # Navigation menu option
    nav = VersionPick({
        Version.lowest(): ['Storage', 'Volumes'],
        '5.8': ['Storage', 'Block Storage', 'Volumes']
    })

    def __init__(self, name, provider, collection):
        self.name = name
        # TODO add storage provider parameter, needed for accurate details nav
        # the storage providers have different names then cloud providers
        # https://bugzilla.redhat.com/show_bug.cgi?id=1455270
        self.provider = provider
        self.collection = collection
        self.appliance = self.collection.appliance

    def wait_for_disappear(self, timeout=300):
        def refresh():
            self.provider.refresh_provider_relationships()
            self.browser.refresh()

        try:
            wait_for(lambda: not self.exists,
                     timeout=timeout,
                     message='Wait for cloud Volume to disappear',
                     delay=20,
                     fail_func=refresh)
        except TimedOutError:
            logger.error(
                'Timed out waiting for Volume to disappear, continuing')

    def delete(self, wait=True):
        """Delete the Volume"""

        view = navigate_to(self, 'Details')
        view.toolbar.configuration.item_select('Delete this Cloud Volume',
                                               handle_alert=True)

        view.entities.flash.assert_success_message(
            'Delete initiated for 1 Cloud Volume.')

        if wait:
            self.wait_for_disappear(500)

    @property
    def exists(self):
        view = navigate_to(self.collection, 'All')
        try:
            view.entities.get_entity(by_name=self.name, surf_pages=True)
            return True
        except ItemNotFound:
            return False
Exemple #10
0
class VMToolbar(View):
    """
    Toolbar view for vms/instances collection destinations
    """
    "Refresh this page"
    reload = Button(title=VersionPick({Version.lowest(): 'Reload current display',
                                       '5.9': 'Refresh this page'}))
    configuration = Dropdown('Configuration')
    policy = Dropdown('Policy')
    lifecycle = Dropdown('Lifecycle')
    power = Dropdown('Power Operations')  # title
    download = Dropdown('Download')

    view_selector = View.nested(ItemsToolBarViewSelector)
class Volume(Navigatable):
    # Navigation menu option
    nav = VersionPick({
        Version.lowest(): ['Storage', 'Volumes'],
        '5.8': ['Storage', 'Block Storage', 'Volumes']
    })

    def __init__(self, name, provider, appliance=None):
        Navigatable.__init__(self, appliance=appliance)
        self.name = name
        # TODO add storage provider parameter, needed for accurate details nav
        # the storage providers have different names then cloud providers
        # https://bugzilla.redhat.com/show_bug.cgi?id=1455270
        self.provider = provider
Exemple #12
0
    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(VersionPick({
            Version.lowest(): 'Remove this PXE Server',
            '5.9': 'Remove this PXE Server from Inventory'}).pick(self.appliance.version),
            handle_alert=not cancel)
        if not cancel:
            main_view = self.create_view(PXEServersView)
            main_view.flash.assert_no_error()
        else:
            navigate_to(self, 'Details')
Exemple #13
0
class Volume(BaseEntity):
    # Navigation menu option
    nav = VersionPick({
        Version.lowest(): ['Storage', 'Volumes'],
        '5.8': ['Storage', 'Block Storage', 'Volumes']
    })

    name = attr.ib()
    provider = attr.ib()

    def wait_for_disappear(self, timeout=300):
        def refresh():
            self.provider.refresh_provider_relationships()
            self.browser.refresh()

        try:
            wait_for(lambda: not self.exists,
                     timeout=timeout,
                     message='Wait for cloud Volume to disappear',
                     delay=20,
                     fail_func=refresh)
        except TimedOutError:
            logger.error(
                'Timed out waiting for Volume to disappear, continuing')

    def delete(self, wait=True):
        """Delete the Volume"""

        view = navigate_to(self, 'Details')
        view.toolbar.configuration.item_select('Delete this Cloud Volume',
                                               handle_alert=True)

        view.entities.flash.assert_success_message(
            'Delete initiated for 1 Cloud Volume.')

        if wait:
            self.wait_for_disappear(500)

    @property
    def exists(self):
        view = navigate_to(self.collection, 'All')
        try:
            view.entities.get_entity(by_name=self.name, surf_pages=True)
            return True
        except ItemNotFound:
            return False
Exemple #14
0
def VolumeEntity():  # noqa
    """Temporary wrapper for Volume Entity during transition to JS based Entity """
    return VersionPick({
        Version.lowest(): NonJSVolumeEntity,
        '5.9': JSBaseEntity,
    })
Exemple #15
0
class PXEDetailsToolBar(PXEToolBar):
    """
     represents the toolbar which appears when any pxe entity is clicked
    """
    reload = Button(title=VersionPick({Version.lowest(): 'Reload current display',
                                       '5.9': 'Refresh this page'}))
Exemple #16
0
class BaseVM(BaseEntity, Pretty, Updateable, PolicyProfileAssignable, Taggable,
             ConsoleMixin):
    """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 = 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
    #
    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 exists(self):
        """Checks presence of the quadicon in the CFME."""
        try:
            navigate_to(self, 'Details')
            return True
        except VmOrInstanceNotFound:
            return False

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

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

        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 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')
        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')
        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)
class Volume(BaseEntity):
    # Navigation menu option
    nav = VersionPick({
        Version.lowest(): ['Storage', 'Volumes'],
        '5.8': ['Storage', 'Block Storage', 'Volumes']
    })

    name = attr.ib()
    provider = attr.ib()

    def wait_for_disappear(self, timeout=300):
        """Wait for disappear the volume"""
        try:
            wait_for(lambda: not self.exists,
                     timeout=timeout,
                     message='Wait for cloud Volume to disappear',
                     delay=20,
                     fail_func=self.refresh)
        except TimedOutError:
            logger.error(
                'Timed out waiting for Volume to disappear, continuing')

    def edit(self, name):
        """Edit cloud volume"""
        view = navigate_to(self, 'Edit')
        view.volume_name.fill(name)
        view.save.click()

        # Wrong flash for 5.7[BZ-1506992]. As BZ clear 5.7 will consistence with 5.8 and 5.9.
        if self.appliance.version < "5.8":
            view.flash.assert_success_message(
                'Updating Cloud Volume "{}"'.format(self.name))
        else:
            view.flash.assert_success_message(
                'Cloud Volume "{}" updated'.format(name))

        self.name = name
        wait_for(lambda: self.exists,
                 delay=20,
                 timeout=500,
                 fail_func=self.refresh)

    def delete(self, wait=True):
        """Delete the Volume"""

        view = navigate_to(self, 'Details')
        view.toolbar.configuration.item_select('Delete this Cloud Volume',
                                               handle_alert=True)

        view.entities.flash.assert_success_message(
            'Delete initiated for 1 Cloud Volume.')

        if wait:
            self.wait_for_disappear(500)

    def refresh(self):
        """Refresh provider relationships and browser"""
        self.provider.refresh_provider_relationships()
        self.browser.refresh()

    def create_backup(self, name, incremental=None, force=None):
        """create backup of cloud volume"""
        view = navigate_to(self, 'Backup')
        view.backup_name.fill(name)
        view.incremental.fill(incremental)
        view.force.fill(force)

        view.save.click()
        view.flash.assert_success_message(
            'Backup for Cloud Volume "{}" created'.format(self.name))

        wait_for(lambda: self.backups > 0,
                 delay=20,
                 timeout=1000,
                 fail_func=self.refresh)

    @property
    def exists(self):
        try:
            navigate_to(self, 'Details')
            return True
        except VolumeNotFoundError:
            return False

    @property
    def size(self):
        """ size of storage cloud volume.

        Returns:
            :py:class:`str' size of volume.
        """
        view = navigate_to(self, 'Details')
        return view.entities.properties.get_text_of('Size')

    @property
    def tenant(self):
        """ cloud tenants for volume.

        Returns:
            :py:class:`str' respective tenants.
        """
        view = navigate_to(self, 'Details')
        return view.entities.relationships.get_text_of('Cloud Tenants')

    @property
    def backups(self):
        """ number of available backups for volume.

        Returns:
            :py:class:`int' backup count.
        """
        view = navigate_to(self, 'Details')
        return int(
            view.entities.relationships.get_text_of('Cloud Volume Backups'))
Exemple #18
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)