""" A model of an Infrastructure Provider in CFME
"""
import attr
from navmazing import NavigateToSibling, NavigateToAttribute
from widgetastic.exceptions import MoveTargetOutOfBoundsException
from widgetastic.utils import Fillable
from widgetastic_patternfly import BreadCrumb
from cfme.base.ui import Server
from cfme.common import TagPageView, Taggable, PolicyProfileAssignable
from cfme.common.host_views import ProviderAllHostsView
from cfme.common.provider import BaseProvider, provider_types, CloudInfraProviderMixin
from cfme.common.provider_views import (
InfraProviderAddView, InfraProviderDetailsView, InfraProvidersDiscoverView,
InfraProvidersView, ProviderEditView, ProviderNodesView,
ProviderTemplatesView, ProviderTimelinesView, ProviderVmsView)
from cfme.exceptions import DestinationNotFound
from cfme.infrastructure.cluster import ClusterView, ClusterToolbar
from cfme.infrastructure.host import HostsCollection
from cfme.infrastructure.virtual_machines import InfraVm, InfraTemplate
from cfme.modeling.base import BaseCollection
from cfme.utils.appliance.implementations.ui import navigator, CFMENavigateStep, navigate_to
from cfme.utils.log import logger
from cfme.utils.pretty import Pretty
from cfme.utils.varmeth import variable
from cfme.utils.wait import wait_for
from widgetastic_manageiq import BaseEntitiesView, View, NoSuchElementException
[docs]class ProviderClustersView(ClusterView):
"""The all view page for clusters open from provider detail page"""
@property
def is_displayed(self):
"""Determine if this page is currently being displayed"""
return (
self.logged_in_as_current_user and
self.entities.title.text == '{p}(All Clusters)'.format(p=self.context['object'].name))
toolbar = View.nested(ClusterToolbar)
breadcrumb = BreadCrumb()
including_entities = View.include(BaseEntitiesView, use_parent=True)
@attr.s(hash=False)
[docs]class InfraProvider(BaseProvider, CloudInfraProviderMixin, Pretty, Fillable,
PolicyProfileAssignable, Taggable):
"""
Abstract model of an infrastructure provider in cfme. See VMwareProvider or RHEVMProvider.
Args:
name: Name of the provider.
details: a details record (see VMwareDetails, RHEVMDetails inner class).
key: The CFME key of the provider in the yaml.
endpoints: one or several provider endpoints like DefaultEndpoint. it should be either dict
in format dict{endpoint.name, endpoint, endpoint_n.name, endpoint_n}, list of endpoints or
mere one endpoint
Usage:
credentials = Credential(principal='bad', secret='reallybad')
endpoint = DefaultEndpoint(hostname='some_host', api_port=65536, credentials=credentials)
myprov = VMwareProvider(name='foo',
region='us-west-1'
endpoints=endpoint)
myprov.create()
"""
STATS_TO_MATCH = ['num_template', 'num_vm', 'num_datastore', 'num_host', 'num_cluster']
SNAPSHOT_TITLE = 'name' # attribute of the provider vm's snapshots used for the title
provider_types = {}
vm_class = InfraVm
template_class = InfraTemplate
category = "infra"
pretty_attrs = ['name', 'key', 'zone']
string_name = "Infrastructure"
templates_destination_name = "Templates"
template_name = "Templates"
db_types = ["InfraManager"]
hosts_menu_item = "Hosts"
vm_name = "Virtual Machines"
name = attr.ib(default=None)
key = attr.ib(default=None)
zone = attr.ib(default=None)
start_ip = attr.ib(default=None)
end_ip = attr.ib(default=None)
provider_data = attr.ib(default=None)
_collections = {'hosts': HostsCollection}
def __attrs_post_init__(self):
super(InfraProvider, self).__attrs_post_init__()
self.parent = self.appliance.collections.infra_providers
@variable(alias='db')
def num_datastore(self):
""" Returns the providers number of templates, as shown on the Details page."""
results = list(self.appliance.db.client.engine.execute(
'SELECT DISTINCT storages.name, hosts.ems_id '
'FROM ext_management_systems ems, hosts, storages st, host_storages hst '
'WHERE hosts.id=hst.host_id AND '
'st.id=hst.storage_id AND '
'hosts.ems_id=ems.id AND '
'ems.name=\'{}\''.format(self.name)))
return len(results)
@num_datastore.variant('ui')
def num_datastore_ui(self):
view = navigate_to(self, "Details")
return int(view.entities.summary("Relationships").get_text_of("Datastores"))
@variable(alias='rest')
def num_host(self):
provider = self.appliance.rest_api.collections.providers.find_by(name=self.name)[0]
num_host = 0
for host in self.appliance.rest_api.collections.hosts:
if host['ems_id'] == provider.id:
num_host += 1
return num_host
@num_host.variant('db')
def num_host_db(self):
ext_management_systems = self.appliance.db.client["ext_management_systems"]
hosts = self.appliance.db.client["hosts"]
hostlist = list(self.appliance.db.client.session.query(hosts.name)
.join(ext_management_systems, hosts.ems_id == ext_management_systems.id)
.filter(ext_management_systems.name == self.name))
return len(hostlist)
@num_host.variant('ui')
def num_host_ui(self):
view = navigate_to(self, "Details")
try:
num = view.entities.summary("Relationships").get_text_of("Hosts")
except NoSuchElementException:
logger.error("Couldn't find number of hosts using key [Hosts] trying Nodes")
num = view.entities.summary("Relationships").get_text_of("Nodes")
return int(num)
@variable(alias='rest')
def num_cluster(self):
provider = self.appliance.rest_api.collections.providers.find_by(name=self.name)[0]
num_cluster = 0
for cluster in self.appliance.rest_api.collections.clusters:
if cluster['ems_id'] == provider.id:
num_cluster += 1
return num_cluster
@num_cluster.variant('db')
def num_cluster_db(self):
""" Returns the providers number of templates, as shown on the Details page."""
ext_management_systems = self.appliance.db.client["ext_management_systems"]
clusters = self.appliance.db.client["ems_clusters"]
clulist = list(self.appliance.db.client.session.query(clusters.name)
.join(ext_management_systems,
clusters.ems_id == ext_management_systems.id)
.filter(ext_management_systems.name == self.name))
return len(clulist)
@num_cluster.variant('ui')
def num_cluster_ui(self):
view = navigate_to(self, "Details")
return int(view.entities.summary("Relationships").get_text_of("Clusters"))
@property
def hosts(self):
return self.collections.hosts
[docs] def get_clusters(self):
"""returns the list of clusters belonging to the provider"""
view = navigate_to(self, 'Clusters')
col = self.appliance.collections.clusters
return [col.instantiate(e.name, self) for e in view.entities.get_all(surf_pages=True)]
[docs] def as_fill_value(self):
return self.name
@property
def view_value_mapping(self):
return {'name': self.name}
[docs] def setup_hosts_credentials(self):
"""Setup credentials for all provider's hosts"""
for host in self.hosts.all():
host_data = None
for data in self.data['hosts']:
if data['name'] == host.name:
host_data = data
break
host.update_credentials_rest(credentials=host_data['credentials'])
[docs] def remove_hosts_credentials(self):
for host in self.hosts.all():
host.remove_credentials_rest()
# todo: update all docstrings
@attr.s
[docs]class InfraProviderCollection(BaseCollection):
"""Collection object for InfraProvider object
"""
ENTITY = InfraProvider
[docs] def all(self):
view = navigate_to(self, 'All')
provs = view.entities.get_all(surf_pages=True)
# trying to figure out provider type and class
# todo: move to all providers collection later
def _get_class(pid):
prov_type = self.appliance.rest_api.collections.providers.get(id=pid)['type']
for prov_class in provider_types('infra').values():
if prov_class.db_types[0] in prov_type:
return prov_class
return [self.instantiate(prov_class=_get_class(p.data['id']), name=p.name) for p in provs]
[docs] def instantiate(self, prov_class, *args, **kwargs):
return prov_class.from_collection(self, *args, **kwargs)
[docs] def create(self, prov_class, *args, **kwargs):
# ugly workaround until I move everything to main class
class_attrs = [at.name for at in attr.fields(prov_class)]
init_kwargs = {}
create_kwargs = {}
for name, value in kwargs.items():
if name not in class_attrs:
create_kwargs[name] = value
else:
init_kwargs[name] = value
obj = self.instantiate(prov_class, *args, **init_kwargs)
obj.create(**create_kwargs)
return obj
[docs] def discover(self, discover_cls, cancel=False, start_ip=None, end_ip=None):
"""
Discover infrastructure providers. Note: only starts discovery, doesn't
wait for it to finish.
Args:
discover_cls: Instance of provider class
cancel: Whether to cancel out of the discover UI.
start_ip: String start of the IP range for discovery
end_ip: String end of the IP range for discovery
"""
form_data = {}
if discover_cls:
form_data.update(discover_cls.discover_dict)
if start_ip:
# TODO: add support of IPv6
for idx, octet in enumerate(start_ip.split('.'), start=1):
key = 'from_ip{idx}'.format(idx=idx)
form_data.update({key: octet})
if end_ip:
end_octet = end_ip.split('.')[-1]
form_data.update({'to_ip4': end_octet})
view = navigate_to(self, 'Discover')
view.fill(form_data)
if cancel:
view.cancel.click()
else:
view.start.click()
[docs] def wait_for_a_provider(self):
view = navigate_to(self, 'All')
logger.info('Waiting for a provider to appear...')
wait_for(lambda: int(view.entities.paginator.items_amount), fail_condition=0,
message="Wait for any provider to appear", num_sec=1000,
fail_func=view.browser.refresh)
@navigator.register(InfraProviderCollection, 'All')
@navigator.register(Server, 'InfraProviders')
@navigator.register(InfraProvider, 'All')
[docs]class All(CFMENavigateStep):
VIEW = InfraProvidersView
prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn')
[docs] def step(self):
self.prerequisite_view.navigation.select('Compute', 'Infrastructure', 'Providers')
[docs] def resetter(self):
# Reset view and selection
self.appliance.browser.widgetastic.browser.refresh()
tb = self.view.toolbar
if 'Grid View' not in tb.view_selector.selected:
tb.view_selector.select('Grid View')
self.view.entities.paginator.reset_selection()
@navigator.register(InfraProviderCollection, 'Add')
@navigator.register(InfraProvider, 'Add')
[docs]class Add(CFMENavigateStep):
VIEW = InfraProviderAddView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
try:
self.prerequisite_view.toolbar.configuration.item_select('Add a New '
'Infrastructure Provider')
except MoveTargetOutOfBoundsException:
# TODO: Remove once fixed 1475303
self.prerequisite_view.toolbar.configuration.item_select('Add a New '
'Infrastructure Provider')
@navigator.register(InfraProviderCollection, 'Discover')
[docs]class Discover(CFMENavigateStep):
VIEW = InfraProvidersDiscoverView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
try:
self.prerequisite_view.toolbar.configuration.item_select('Discover '
'Infrastructure Providers')
except MoveTargetOutOfBoundsException:
# TODO: Remove once fixed 1475303
self.prerequisite_view.toolbar.configuration.item_select('Discover '
'Infrastructure Providers')
@navigator.register(InfraProvider, 'Details')
[docs]class Details(CFMENavigateStep):
VIEW = InfraProviderDetailsView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.entities.get_entity(name=self.obj.name, surf_pages=True).click()
[docs] def resetter(self):
"""Reset view and selection"""
view_selector = self.view.toolbar.view_selector
if view_selector.selected != 'Summary View':
view_selector.select('Summary View')
@navigator.register(InfraProvider, 'EditTags')
@navigator.register(InfraProvider, 'Edit')
[docs]class Edit(CFMENavigateStep):
VIEW = ProviderEditView
prerequisite = NavigateToSibling('All')
[docs] def step(self):
self.prerequisite_view.entities.get_entity(name=self.obj.name, surf_pages=True).check()
try:
self.prerequisite_view.toolbar.configuration.item_select(
'Edit Selected Infrastructure Providers')
except MoveTargetOutOfBoundsException:
# TODO: Remove once fixed 1475303
self.prerequisite_view.toolbar.configuration.item_select(
'Edit Selected Infrastructure Providers')
@navigator.register(InfraProvider, 'Timelines')
[docs]class Timelines(CFMENavigateStep):
VIEW = ProviderTimelinesView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
mon = self.prerequisite_view.toolbar.monitoring
try:
mon.item_select('Timelines')
except MoveTargetOutOfBoundsException:
# TODO: Remove once fixed 1475303
mon.item_select('Timelines')
@navigator.register(InfraProvider, 'Clusters')
[docs]class DetailsFromProvider(CFMENavigateStep):
VIEW = ProviderClustersView
prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs):
"""Navigate to the correct view"""
self.prerequisite_view.entities.summary('Relationships').click_at('Clusters')
@navigator.register(InfraProvider, 'Hosts')
[docs]class ProviderHosts(CFMENavigateStep):
VIEW = ProviderAllHostsView
prerequisite = NavigateToSibling('Details')
[docs] def step(self, *args, **kwargs):
self.prerequisite_view.entities.summary('Relationships').click_at('Hosts')
@navigator.register(InfraProvider, 'ProviderNodes') # matching other infra class destinations
[docs]class ProviderNodes(CFMENavigateStep):
VIEW = ProviderNodesView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
try:
self.prerequisite_view.entities.summary('Relationships').click_at(
self.obj.hosts_menu_item)
except NameError:
raise DestinationNotFound(
"{} aren't present on details page of this provider"
.format(self.obj.hosts_menu_item))
@navigator.register(InfraProvider, 'ProviderTemplates')
[docs]class ProviderTemplates(CFMENavigateStep):
VIEW = ProviderTemplatesView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.entities.summary('Relationships').click_at('Templates')
@navigator.register(InfraProvider, 'ProviderVms')
[docs]class ProviderVms(CFMENavigateStep):
VIEW = ProviderVmsView
prerequisite = NavigateToSibling('Details')
[docs] def step(self):
self.prerequisite_view.entities.summary('Relationships').click_at('Virtual Machines')