Source code for cfme.cloud.instance

from navmazing import NavigateToSibling, NavigateToAttribute
from riggerlib import recursive_update
from widgetastic.exceptions import NoSuchElementException
from widgetastic.widget import View
from widgetastic_patternfly import Dropdown, Button
from widgetastic_manageiq import ManageIQTree, TimelinesView, Accordion

from cfme.base.login import BaseLoggedInPage
from cfme.common.vm import VM
from cfme.common.vm_views import (
    ProvisionView, VMToolbar, VMEntities, VMDetailsEntities, RetirementView, EditView,
    SetOwnershipView, ManagementEngineView, ManagePoliciesView,
    PolicySimulationView)
from cfme.exceptions import InstanceNotFound, ItemNotFound
from cfme.web_ui import flash
from cfme.utils.appliance import Navigatable
from cfme.utils.appliance.implementations.ui import navigate_to, CFMENavigateStep, navigator
from cfme.utils.log import logger
from cfme.utils.wait import wait_for


[docs]class InstanceDetailsToolbar(View): """ The toolbar on the details screen for an instance """ reload = Button(title='Reload current display') configuration = Dropdown('Configuration') policy = Dropdown('Policy') lifecycle = Dropdown('Lifecycle') monitoring = Dropdown('Monitoring') power = Dropdown('Instance Power Functions') # title download = Button(title='Download summary in PDF format') access = Dropdown("Access")
[docs]class InstanceAccordion(View): """ The accordion on the instances page """ @View.nested class instances_by_provider(Accordion): # noqa ACCORDION_NAME = 'Instances by Provider' tree = ManageIQTree() @View.nested class images_by_provider(Accordion): # noqa ACCORDION_NAME = 'Images by Provider' tree = ManageIQTree() @View.nested class instances(Accordion): # noqa ACCORDION_NAME = 'Instances' tree = ManageIQTree() @View.nested class images(Accordion): # noqa ACCORDION_NAME = 'Images' tree = ManageIQTree()
[docs]class CloudInstanceView(BaseLoggedInPage): """Base view for header/nav check, inherit for navigatable views""" @property def in_cloud_instance(self): return ( self.logged_in_as_current_user and self.navigation.currently_selected == ['Compute', 'Clouds', 'Instances'] )
[docs]class InstanceAllView(CloudInstanceView): """ The collection page for instances """ @property def is_displayed(self): return ( self.in_cloud_instance and self.entities.title.text == 'All Instances' and self.sidebar.instances.is_opened) toolbar = View.nested(VMToolbar) sidebar = View.nested(InstanceAccordion) including_entities = View.include(VMEntities, use_parent=True)
[docs]class InstanceProviderAllView(CloudInstanceView): @property def is_displayed(self): return ( self.in_cloud_instance and self.entities.title.text == 'Instances under Provider "{}"' .format(self.context['object'].provider.name) and self.sidebar.instances_by_provider.is_opened) toolbar = View.nested(VMToolbar) sidebar = View.nested(InstanceAccordion) including_entities = View.include(VMEntities, use_parent=True) @View.nested class instances_by_provider(Accordion): # noqa ACCORDION_NAME = 'Instances by Provider' tree = ManageIQTree()
[docs]class InstanceDetailsView(CloudInstanceView): @property def is_displayed(self): expected_name = self.context['object'].name expected_provider = self.context['object'].provider.name try: # Not displayed when the instance is archived relationship_provider_name = self.entities.relationships.get_text_of('Cloud Provider') except (NameError, NoSuchElementException): logger.warning('No "Cloud Provider" Relationship, assume instance view not displayed') return False return ( self.in_cloud_instance and self.entities.title.text == 'Instance "{}"'.format(expected_name) and relationship_provider_name == expected_provider) toolbar = View.nested(InstanceDetailsToolbar) sidebar = View.nested(InstanceAccordion) entities = View.nested(VMDetailsEntities)
[docs]class InstanceTimelinesView(CloudInstanceView, TimelinesView): @property def is_displayed(self): return ( self.in_cloud_instance and super(TimelinesView, self).is_displayed)
[docs]class Instance(VM, Navigatable): """Represents a generic instance in CFME. This class is used if none of the inherited classes will match. Args: name: Name of the instance provider: :py:class:`cfme.cloud.provider.Provider` object template_name: Name of the template to use for provisioning appliance: :py:class: `utils.appliance.IPAppliance` object Note: This class cannot be instantiated. Use :py:func:`instance_factory` instead. """ ALL_LIST_LOCATION = "clouds_instances" TO_RETIRE = "Retire this Instance" QUADICON_TYPE = "instance" VM_TYPE = "Instance" 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') REMOVE_SINGLE = 'Remove Instance' TO_OPEN_EDIT = "Edit this Instance" def __init__(self, name, provider, template_name=None, appliance=None): super(Instance, self).__init__(name=name, provider=provider, template_name=template_name) Navigatable.__init__(self, appliance=appliance)
[docs] def create(self, form_values, cancel=False): """Provisions an instance with the given properties through CFME Args: form_values: dictionary of form values for provisioning, structured into tabs Note: Calling create on a sub-class of instance will generate the properly formatted dictionary when the correct fields are supplied. """ view = navigate_to(self, 'Provision') # Only support 1 security group for now # TODO: handle multiple if 'environment' in form_values and 'security_groups' in form_values['environment'] and \ isinstance(form_values['environment']['security_groups'], (list, tuple)): first_group = form_values['environment']['security_groups'][0] recursive_update(form_values, {'environment': {'security_groups': first_group}}) view.form.fill(form_values) if cancel: view.form.cancel_button.click() # Redirects to Instance All view = self.browser.create_view(InstanceAllView) wait_for(lambda: view.is_displayed, timeout=10, delay=2, message='wait for redirect') view.entities.flash.assert_success_message(self.PROVISION_CANCEL) view.entities.flash.assert_no_error() else: view.form.submit_button.click() # TODO this redirects to service.request, create_view on it when it exists for flash wait_for(flash.get_messages, fail_condition=[], timeout=10, delay=2, message='wait for Flash Success') flash.assert_success_message(self.PROVISION_START)
[docs] def update(self, values, cancel=False, reset=False): """Update cloud instance Args: values: Dictionary of form key/value pairs cancel: Boolean, cancel the form submission reset: Boolean, reset form after fill - returns immediately after reset Note: The edit form contains a 'Reset' button - if this is c """ view = navigate_to(self, 'Edit') # form is the view's parent view.form.fill(values) if reset: view.form.reset_button.click() return else: button = view.form.cancel_button if cancel else view.form.submit_button button.click()
[docs] def on_details(self, force=False): """A function to determine if the browser is already on the proper instance details page. An instance may not be assigned to a provider if archived or orphaned If no provider is listed, default to False since we may be on the details page for an instance on the wrong provider. """ if not force: return self.browser.create_view(InstanceDetailsView).is_displayed else: navigate_to(self, 'Details') return True
[docs] def get_vm_via_rest(self): # Try except block, because instances collection isn't available on 5.4 try: instance = self.appliance.rest_api.collections.instances.get(name=self.name) except AttributeError: raise Exception("Collection instances isn't available") else: return instance
[docs] def get_collection_via_rest(self): return self.appliance.rest_api.collections.instances
[docs] def wait_for_instance_state_change(self, desired_state, timeout=900): """Wait for an instance to come to desired state. This function waits just the needed amount of time thanks to wait_for. Args: desired_state: A string or list of strings indicating desired state timeout: Specify amount of time (in seconds) to wait until TimedOutError is raised """ def _looking_for_state_change(): view = navigate_to(self, 'Details') current_state = view.entities.power_management.get_text_of("Power State") logger.info('Current Instance state: {}'.format(current_state)) logger.info('Desired Instance state: {}'.format(desired_state)) if isinstance(desired_state, (list, tuple)): return current_state in desired_state else: return current_state == desired_state return wait_for(_looking_for_state_change, num_sec=timeout, delay=15, message='Checking for instance state change', fail_func=self.provider.refresh_provider_relationships, handle_exception=True)
[docs] def find_quadicon(self, **kwargs): """Find and return a quadicon belonging to a specific instance TODO: remove this method and refactor callers to use view entities instead Args: Returns: entity of appropriate type """ view = navigate_to(self, 'All') view.toolbar.view_selector.select('Grid View') try: return view.entities.get_entity(by_name=self.name, surf_pages=True) except ItemNotFound: raise InstanceNotFound("Instance '{}' not found in UI!".format(self.name))
@property def exists(self): try: navigate_to(self, 'Details') return True except InstanceNotFound: return False
[docs] def power_control_from_cfme(self, *args, **kwargs): """Power controls a VM from within CFME using details or collection Raises: InstanceNotFound: the instance wasn't found when navigating OptionNotAvailable: option param is not visible or enabled """ # TODO push this to common.vm when infra vm classes have widgets if not kwargs.get('option'): raise ValueError('Need to provide option for power_control_from_cfme, no default.') if kwargs.get('from_details', True): view = navigate_to(self, 'Details') else: view = navigate_to(self, 'AllForProvider') view.toolbar.view_selector.select('List View') try: row = view.entities.get_entity(by_name=self.name) except ItemNotFound: raise InstanceNotFound('Failed to find instance in table: {}'.format(self.name)) row.check() # cancel is the kwarg, when true we want item_select to dismiss the alert, flip the bool view.toolbar.power.item_select(kwargs.get('option'), handle_alert=not kwargs.get('cancel', False))
[docs] def set_ownership(self, user=None, group=None, click_cancel=False, click_reset=False): """Set instance ownership TODO: collapse this back to common.vm after both subclasses converted to widgetastic 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.flash.assert_no_error() view.form.cancel_button.click() view = self.create_view(InstanceDetailsView) view.flash.assert_success_message('Set Ownership was cancelled by the user') view.flash.assert_no_error() return # Only if 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') view.flash.assert_no_error() else: # save the form view.form.save_button.click() view = self.create_view(InstanceDetailsView) view.flash.assert_success_message('Ownership saved for selected {}' .format(self.VM_TYPE)) view.flash.assert_no_error()
[docs] 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(InstanceDetailsView) view.flash.assert_no_error() view.flash.assert_success_message(msg)
@navigator.register(Instance, 'All')
[docs]class All(CFMENavigateStep): VIEW = InstanceAllView prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn')
[docs] def step(self): self.prerequisite_view.navigation.select('Compute', 'Clouds', 'Instances') self.view.sidebar.instances.tree.click_path('All Instances')
[docs] def resetter(self, *args, **kwargs): # If a filter was applied, it will persist through navigation and needs to be cleared if self.view.adv_search_clear.is_displayed: logger.debug('Clearing advanced search filter') self.view.adv_search_clear.click() self.view.toolbar.reload.click()
@navigator.register(Instance, 'AllForProvider')
[docs]class AllForProvider(CFMENavigateStep): VIEW = InstanceProviderAllView prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.navigation.select('Compute', 'Clouds', 'Instances') self.view.sidebar.instances_by_provider.tree.click_path('Instances by Provider', self.obj.provider.name)
[docs] def resetter(self, *args, **kwargs): # If a filter was applied, it will persist through navigation and needs to be cleared if self.view.adv_search_clear.is_displayed: logger.debug('Clearing advanced search filter') self.view.adv_search_clear.click() self.view.toolbar.reload.click()
@navigator.register(Instance, 'Details')
[docs]class Details(CFMENavigateStep): VIEW = InstanceDetailsView prerequisite = NavigateToSibling('AllForProvider')
[docs] def step(self): self.prerequisite_view.toolbar.view_selector.select('List View') try: row = self.prerequisite_view.entities.get_entity(by_name=self.obj.name, surf_pages=True) except ItemNotFound: raise InstanceNotFound('Failed to locate instance with name "{}"'.format(self.obj.name)) row.click()
[docs] def resetter(self, *args, **kwargs): self.view.toolbar.reload.click()
@navigator.register(Instance, 'Edit')
[docs]class Edit(CFMENavigateStep): VIEW = EditView prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.configuration.item_select('Edit this Instance')
@navigator.register(Instance, 'EditManagementEngineRelationship')
[docs]class EditManagementEngineRelationship(CFMENavigateStep): VIEW = ManagementEngineView prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs): configuration = self.prerequisite_view.toolbar.configuration configuration.item_select('Edit Management Engine Relationship')
@navigator.register(Instance, 'ManagePolicies')
[docs]class ManagePolicies(CFMENavigateStep): VIEW = ManagePoliciesView prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.policy.item_select('Manage Policies')
@navigator.register(Instance, 'Provision')
[docs]class Provision(CFMENavigateStep): VIEW = ProvisionView prerequisite = NavigateToSibling('All')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.lifecycle.item_select('Provision Instances')
@navigator.register(Instance, 'PolicySimulation')
[docs]class PolicySimulation(CFMENavigateStep): VIEW = PolicySimulationView prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.policy.item_select('Policy Simulation')
@navigator.register(Instance, 'SetOwnership')
[docs]class SetOwnership(CFMENavigateStep): VIEW = SetOwnershipView prerequisite = NavigateToSibling('Details') # No am_i_here because the page only indicates name and not provider
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.configuration.item_select('Set Ownership')
@navigator.register(Instance, 'SetRetirement')
[docs]class SetRetirement(CFMENavigateStep): VIEW = RetirementView prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.lifecycle.item_select('Set Retirement Date')
@navigator.register(Instance, 'Timelines')
[docs]class Timelines(CFMENavigateStep): VIEW = InstanceTimelinesView prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs): self.prerequisite_view.toolbar.monitoring.item_select('Timelines')