diff --git a/cfme/ansible_tower/explorer.py b/cfme/ansible_tower/explorer.py index cd6afb610b..554ff737c5 100644 --- a/cfme/ansible_tower/explorer.py +++ b/cfme/ansible_tower/explorer.py @@ -11,6 +11,7 @@ from cfme.common import Taggable from cfme.common import TaggableCollection from cfme.exceptions import ItemNotFound +from cfme.infrastructure.config_management.config_systems.ansible_tower import AnsibleTowerSystemsCollection # noqa from cfme.modeling.base import BaseCollection from cfme.modeling.base import BaseEntity from cfme.utils.appliance.implementations.ui import CFMENavigateStep @@ -150,26 +151,6 @@ def is_displayed(self): ) -@attr.s -class AnsibleTowerProvider(BaseEntity): - pass - - -@attr.s -class AnsibleTowerProvidersCollection(BaseCollection): - ENTITY = AnsibleTowerProvider - - -@attr.s -class AnsibleTowerSystem(BaseEntity): - pass - - -@attr.s -class AnsibleTowerSystemsCollection(BaseCollection): - ENTITY = AnsibleTowerSystem - - @attr.s class AnsibleTowerJobTemplate(BaseEntity, Taggable): name = attr.ib() @@ -209,15 +190,6 @@ def step(self, *args, **kwargs): self.view.sidebar.providers.tree.click_path('All Ansible Tower Providers') -@navigator.register(AnsibleTowerProvidersCollection, 'All') -class AnsibleTowerExplorerProvidersAll(CFMENavigateStep): - VIEW = TowerExplorerProvidersAllView - prerequisite = NavigateToAttribute('appliance.server', 'AnsibleTowerExplorer') - - def step(self, *args, **kwargs): - self.view.sidebar.providers.tree.click_path('All Ansible Tower Providers') - - @navigator.register(AnsibleTowerSystemsCollection, 'All') class TowerExplorerSystemAll(CFMENavigateStep): VIEW = TowerExplorerSystemsAllView diff --git a/cfme/infrastructure/config_management.py b/cfme/infrastructure/config_management/__init__.py similarity index 58% rename from cfme/infrastructure/config_management.py rename to cfme/infrastructure/config_management/__init__.py index 7ab64676ff..4f8501ea2f 100644 --- a/cfme/infrastructure/config_management.py +++ b/cfme/infrastructure/config_management/__init__.py @@ -1,8 +1,9 @@ +import attr from manageiq_client.api import APIException from manageiq_client.api import Entity as RestEntity from navmazing import NavigateToAttribute from navmazing import NavigateToSibling -from widgetastic.exceptions import NoSuchElementException +from navmazing import NavigationDestinationNotFound from widgetastic.widget import Checkbox from widgetastic.widget import Text from widgetastic.widget import TextInput @@ -12,12 +13,14 @@ from cfme.base.credential import Credential as BaseCredential from cfme.common import BaseLoggedInPage -from cfme.common import Taggable from cfme.common import TagPageView +from cfme.common.provider import BaseProvider from cfme.exceptions import displayed_not_implemented -from cfme.utils import conf +from cfme.infrastructure.config_management.config_profiles import ConfigProfile +from cfme.infrastructure.config_management.config_profiles import ConfigProfilesCollection +from cfme.infrastructure.config_management.config_systems import ConfigSystem +from cfme.modeling.base import BaseCollection from cfme.utils import ParamClassName -from cfme.utils.appliance import NavigatableMixin from cfme.utils.appliance.implementations.ui import CFMENavigateStep from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.appliance.implementations.ui import navigator @@ -25,9 +28,6 @@ from cfme.utils.pretty import Pretty from cfme.utils.rest import assert_response from cfme.utils.update import Updateable -from cfme.utils.version import LATEST -from cfme.utils.version import LOWEST -from cfme.utils.version import VersionPicker from cfme.utils.wait import wait_for from widgetastic_manageiq import Accordion from widgetastic_manageiq import BaseEntitiesView @@ -83,24 +83,6 @@ class ConfigManagementEntities(BaseEntitiesView): table = Table("//div[@id='gtl_div']//table") -class ConfigManagementProfileEntities(BaseEntitiesView): - """Entities view for the detail page""" - @View.nested - class summary(WaitTab): # noqa - TAB_NAME = 'Summary' - - properties = SummaryTable(title='Properties') - environment = SummaryTable(title='Environment') - operating_system = SummaryTable(title='Operating System') - tenancy = SummaryTable(title='Tenancy') - smart_management = SummaryTable(title='Smart Management') - - @View.nested - class configured_systems(WaitTab): # noqa - TAB_NAME = 'Configured Systems' - elements = Table('//div[@id="main_div"]//div[@id="list_grid" or @id="gtl_div"]//table') - - class ConfigManagementAddForm(View): """Form to add a provider""" name = TextInput('name') @@ -147,13 +129,14 @@ class ConfigManagementEditEntities(View): cancel = Button('Cancel') -class ConfigManagementView(BaseLoggedInPage): - """The base page for both the all and details page""" +class ConfigManagementCollectionView(BaseLoggedInPage): + """ Base page for ALL """ @property def in_config(self): """Determine if we're in the config section""" - if getattr(self.context['object'], 'type', None) == 'Ansible Tower': + object_type = getattr(self.context['object'], 'type', None) + if object_type == 'ansible_tower': nav_chain = ['Automation', 'Ansible Tower', 'Explorer'] else: nav_chain = ['Configuration', 'Management'] @@ -163,39 +146,20 @@ def in_config(self): ) -class ConfigManagementAllView(ConfigManagementView): - """The main list view""" - toolbar = View.nested(ConfigManagementToolbar) - sidebar = View.nested(ConfigManagementSideBar) - search = View.nested(Search) - including_entities = View.include(ConfigManagementEntities, use_parent=True) - - @property - def is_displayed(self): - """Is this view being displayed?""" - if self.context['object'].type == 'Ansible Tower': - title_text = 'All Ansible Tower Providers' - else: - title_text = 'All Configuration Management Providers' - return ( - self.in_config and - self.entities.title.text == title_text - ) - - -class ConfigSystemAllView(ConfigManagementAllView): - """The config system view has a different title""" +class ConfigManagementView(BaseLoggedInPage): + """The base page for the details page""" @property - def is_displayed(self): - if hasattr(self.context['object'], 'type') and self.context['object'].type == \ - 'Ansible Tower': - title_text = 'All Ansible Tower Configured Systems' + def in_config(self): + """Determine if we're in the config section""" + object_type = getattr(self.context['object'], 'type', None) + if object_type == 'ansible_tower': + nav_chain = ['Automation', 'Ansible Tower', 'Explorer'] else: - title_text = 'All Configured Systems' + nav_chain = ['Configuration', 'Management'] return ( - self.in_config and - self.entities.title.text == title_text + self.logged_in_as_current_user and + self.navigation.currently_selected == nav_chain ) @@ -219,21 +183,6 @@ def is_displayed(self): ) -class ConfigManagementProfileView(ConfigManagementView): - """The profile page""" - toolbar = View.nested(ConfigManagementDetailsToolbar) - sidebar = View.nested(ConfigManagementSideBar) - including_entities = View.include(ConfigManagementProfileEntities, use_parent=True) - - @property - def is_displayed(self): - """Is this view being displayed?""" - title = 'Configured System ({}) "{}"'.format( - self.context['object'].manager.type, - self.context['object'].name) - return self.in_config and self.entities.title.text == title - - class ConfigManagementAddView(ConfigManagementView): """The add page""" sidebar = View.nested(ConfigManagementSideBar) @@ -247,17 +196,64 @@ class ConfigManagementEditView(ConfigManagementView): entities = View.nested(ConfigManagementEditEntities) is_displayed = displayed_not_implemented - is_displayed = displayed_not_implemented + +class ConfigManagementProfileEntities(BaseEntitiesView): + """Entities view for the detail page""" + @View.nested + class summary(WaitTab): # noqa + TAB_NAME = 'Summary' + + properties = SummaryTable(title='Properties') + environment = SummaryTable(title='Environment') + operating_system = SummaryTable(title='Operating System') + tenancy = SummaryTable(title='Tenancy') + smart_management = SummaryTable(title='Smart Management') + + @View.nested + class configured_systems(WaitTab): # noqa + TAB_NAME = 'Configured Systems' + elements = Table('//div[@id="main_div"]//div[@id="list_grid" or @id="gtl_div"]//table') -class ConfigManager(Updateable, Pretty, NavigatableMixin): +class ConfigManagerProvidersAllView(ConfigManagementCollectionView): + """The main list view""" + toolbar = View.nested(ConfigManagementToolbar) + sidebar = View.nested(ConfigManagementSideBar) + search = View.nested(Search) + including_entities = View.include(ConfigManagementEntities, use_parent=True) + + @property + def is_displayed(self): + """Is this view being displayed?""" + title_text = 'All Configuration Management Providers' + return ( + self.in_config and + self.entities.title.text == title_text + ) + + +class ConfigManagementProfileView(ConfigManagementView): + """The profile page""" + toolbar = View.nested(ConfigManagementDetailsToolbar) + sidebar = View.nested(ConfigManagementSideBar) + including_entities = View.include(ConfigManagementProfileEntities, use_parent=True) + + @property + def is_displayed(self): + title = 'Configured Systems under {} "{}"'.format( + self.context['object'].type, + self.context['object'].name) + return self.entities.title.text == title + + +@attr.s(eq=False) +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 - 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) @@ -265,31 +261,33 @@ class ConfigManager(Updateable, Pretty, NavigatableMixin): Use Satellite or AnsibleTower classes instead. """ - pretty_attr = ['name', 'url'] + pretty_attr = ['name', 'key'] _param_name = ParamClassName('name') - type = None + 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 - def __init__(self, appliance, name=None, url=None, ssl=None, credentials=None, key=None): - self.appliance = appliance - self.name = name - self.url = url - self.ssl = ssl - self.credentials = credentials - self.key = key or name + _collections = {"config_profiles": ConfigProfilesCollection} 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 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): + def create(self, cancel=False, validate_credentials=True, validate=True, force=False, **kwargs): """Creates the manager through UI Args: @@ -302,22 +300,24 @@ def create(self, cancel=False, validate_credentials=True, validate=True, force=F 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'])) + 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.yaml_data['config_profiles']]) + [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) - 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: @@ -329,12 +329,12 @@ def config_profiles_loaded(): else: view.entities.add.wait_displayed('2s') view.entities.add.click() - success_message = '{} Provider "{}" was added'.format(self.type, self.name) + 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.yaml_data['config_profiles'] + self.data['config_profiles'] except KeyError as e: logger.exception(e) raise @@ -343,7 +343,8 @@ def config_profiles_loaded(): config_profiles_loaded, fail_func=self.refresh_relationships, handle_exception=True, - num_sec=180, delay=30) + num_sec=180, delay=30 + ) def update(self, updates, cancel=False, validate_credentials=False): """Updates the manager through UI @@ -369,7 +370,7 @@ def update(self, updates, cancel=False, validate_credentials=False): else: view.entities.save.click() view.flash.assert_success_message( - '{} Provider "{}" was updated'.format(self.type, updates['name'] or self.name)) + '{} 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): @@ -385,8 +386,7 @@ def delete(self, cancel=False, wait_deleted=True, force=False): """ if not force and not self.exists: return - view = navigate_to(self, 'All') - view.toolbar.view_selector.select('List View') + 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' @@ -413,32 +413,35 @@ def rest_api_entity(self): 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) - ) - }, - ) + 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""" - if "ansible_tower" in self.key: + include_ssl = False + if self.type == "ansible_tower": config_type = "AnsibleTower" - elif "satellite" in self.key: + else: config_type = "Foreman" + include_ssl = True 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"], }, } + + if include_ssl: + payload["verify_ssl"] = self.ssl + try: self.appliance.rest_api.post( api_endpoint_url=self.appliance.url_path( @@ -471,18 +474,9 @@ def delete_rest(self): "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 = 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() @@ -495,244 +489,46 @@ def refresh_relationships(self, cancel=False): @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(appliance=self.appliance, name=name, manager=self)) - return config_profiles + return self.collections.config_profiles.all() @property - def systems(self): + def config_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, appliance): - """Returns 'ConfigManager' object loaded from yamls, based on its key""" - data = conf.cfme_data.configuration_managers[key] - creds = conf.credentials[data['credentials']] - return cls( - appliance=appliance, - name=data['name'], - url=data['url'], - ssl=data['ssl'], - credentials=cls.Credential( - principal=creds['username'], secret=creds['password']), - key=key) + 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): - if self.type == 'Ansible Tower': - return '{} Automation Manager'.format(self.name) - else: - return '{} Configuration Manager'.format(self.name) - - -def get_config_manager_from_config(cfg_mgr_key, appliance=None, mgr_type=None): - cfg_mgr = conf.cfme_data.get('configuration_managers', {})[cfg_mgr_key] - if mgr_type and cfg_mgr['type'] != mgr_type: - logger.info(f'Config managers loading type mismatch: {cfg_mgr} ' - f'key does not match desired type: [{mgr_type}]') - return None - if cfg_mgr['type'] == 'satellite': - return Satellite.load_from_yaml(cfg_mgr_key, appliance) - elif cfg_mgr['type'] == 'ansible': - return AnsibleTower.load_from_yaml(cfg_mgr_key, appliance) - else: - raise Exception("Unknown configuration manager key") - + return self.ui_name -class ConfigProfile(Pretty, NavigatableMixin): - """Configuration profile object (foreman-side hostgroup) - Args: - appliance: appliance object - name: Name of the profile - manager: ConfigManager object which this profile is bound to - """ - pretty_attrs = ['name', 'manager'] - - def __init__(self, appliance, name, manager): - self.appliance = appliance - self.name = name - self.manager = manager - - @property - def systems(self): - """Returns 'ConfigSystem' objects that are active under this profile""" - view = navigate_to(self, 'Details') - view.toolbar.view_selector.select('List View') - - # Unassigned config profile has no tabstrip - if 'unassigned' not in self.name.lower(): - view.entities.configured_systems.click() - - if view.entities.configured_systems.elements.is_displayed: - return [ConfigSystem(appliance=self.appliance, name=row.hostname.text, profile=self) - for row in view.entities.configured_systems.elements] - return list() - - -class ConfigSystem(Pretty, NavigatableMixin, Taggable): - """The tags pages of the config system""" - pretty_attrs = ['name', 'manager_key'] - - def __init__(self, appliance, name, profile): - self.appliance = appliance - self.name = name - self.profile = profile - - def get_tags(self, tenant="My Company Tags"): - """Overridden get_tags method to deal with the fact that configured systems don't have a - details view.""" - view = navigate_to(self, 'EditTags') - return [ - self.appliance.collections.categories.instantiate( - display_name=r.category.text.replace('*', '').strip()).collections.tags.instantiate( - display_name=r.assigned_value.text.strip()) - for r in view.form.tags - ] - - -class Satellite(ConfigManager): - """ - Configuration manager object (Red Hat Satellite, Foreman) - - Args: - name: Name of the Satellite/Foreman configuration manager - url: URL, hostname or IP of the configuration 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: - Create provider: - .. code-block:: python +class ConfigManagerProviderCollection(BaseCollection): - satellite_cfg_mgr = Satellite('my_satellite', 'my-satellite.example.com', - ssl=False, ConfigManager.Credential(principal='admin', - secret='testing'), key='satellite_yaml_key') - satellite_cfg_mgr.create() + ENTITY = ConfigManagerProvider - Update provider: - .. code-block:: python + def instantiate(self, prov_class, *args, **kwargs): + return prov_class.from_collection(self, *args, **kwargs) - with update(satellite_cfg_mgr): - satellite_cfg_mgr.name = 'new_satellite_name' - - Delete provider: - .. code-block:: python - - satellite_cfg_mgr.delete() - """ + def create(self, *args, **kwargs): + """ Create/add config manager via UI """ + config_manager = self.instantiate(*args, **kwargs) + config_manager.create(**kwargs) + return config_manager - type = VersionPicker({ - LOWEST: 'Red Hat Satellite', - LATEST: 'Foreman' - }) - def __init__(self, appliance, name=None, url=None, ssl=None, credentials=None, key=None): - super(Satellite, self).__init__(appliance=appliance, - name=name, - url=url, - ssl=ssl, - credentials=credentials, - key=key) - self.key = key or name - - -class AnsibleTower(ConfigManager): - """ - Configuration manager object (Ansible Tower) - - Args: - name: Name of the Ansible Tower configuration manager - url: URL, hostname or IP of the configuration 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: - Create provider: - .. code-block:: python - - tower_cfg_mgr = AnsibleTower('my_tower', 'https://my-tower.example.com/api/v1', - ssl=False, ConfigManager.Credential(principal='admin', - secret='testing'), key='tower_yaml_key') - tower_cfg_mgr.create() - - Update provider: - .. code-block:: python - - with update(tower_cfg_mgr): - tower_cfg_mgr.name = 'new_tower_name' - - Delete provider: - .. code-block:: python - - tower_cfg_mgr.delete() - """ - - type = 'Ansible Tower' - - def __init__(self, appliance, name=None, url=None, ssl=None, credentials=None, key=None): - super(AnsibleTower, self).__init__(appliance=appliance, - name=name, - url=url, - ssl=ssl, - credentials=credentials, - key=key) - self.key = key or name - - @property - def ui_name(self): - """Return the name used in the UI""" - return '{} Automation Manager'.format(self.name) - - -@navigator.register(ConfigManager, 'All') -class MgrAll(CFMENavigateStep): - VIEW = ConfigManagementAllView - prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn') - - def step(self, *args, **kwargs): - if self.obj.type == 'Ansible Tower': - self.prerequisite_view.navigation.select('Automation', 'Ansible Tower', 'Explorer') - self.view.sidebar.providers.tree.click_path('All Ansible Tower Providers') - else: - self.prerequisite_view.navigation.select('Configuration', 'Management') - self.view.sidebar.providers.tree.click_path('All Configuration Manager Providers') - - -@navigator.register(ConfigManager, 'Add') +@navigator.register(ConfigManagerProvider, 'Add') class MgrAdd(CFMENavigateStep): VIEW = ConfigManagementAddView - prerequisite = NavigateToSibling('All') + prerequisite = NavigateToSibling('AllOfType') def step(self, *args, **kwargs): self.prerequisite_view.toolbar.configuration.item_select('Add a new Provider') -@navigator.register(ConfigManager, 'Edit') +@navigator.register(ConfigManagerProvider, 'Edit') class MgrEdit(CFMENavigateStep): VIEW = ConfigManagementEditView - prerequisite = NavigateToSibling('All') + prerequisite = NavigateToSibling('AllOfType') def step(self, *args, **kwargs): self.prerequisite_view.toolbar.view_selector.select('List View') @@ -742,10 +538,10 @@ def step(self, *args, **kwargs): self.prerequisite_view.toolbar.configuration.item_select('Edit this Provider') -@navigator.register(ConfigManager, 'Details') +@navigator.register(ConfigManagerProvider, 'Details') class MgrDetails(CFMENavigateStep): VIEW = ConfigManagementDetailsView - prerequisite = NavigateToSibling('All') + prerequisite = NavigateToSibling('AllOfType') def step(self, *args, **kwargs): self.prerequisite_view.toolbar.view_selector.select('List View') @@ -754,50 +550,52 @@ def step(self, *args, **kwargs): row.click() -@navigator.register(ConfigManager, 'EditFromDetails') +@navigator.register(ConfigManagerProvider, 'EditFromDetails') class MgrEditFromDetails(CFMENavigateStep): VIEW = ConfigManagementEditView - prerequisite = NavigateToSibling('Details') + prerequisite = NavigateToSibling('AllOfType') def step(self, *args, **kwargs): self.prerequisite_view.toolbar.configuration.item_select('Edit this Provider') -@navigator.register(ConfigProfile, 'Details') -class Details(CFMENavigateStep): - VIEW = ConfigManagementProfileView - prerequisite = NavigateToAttribute('manager', 'Details') +@navigator.register(ConfigSystem, 'EditTags') +class SysEditTags(CFMENavigateStep): + VIEW = TagPageView + prerequisite = NavigateToAttribute('profile', 'Details') def step(self, *args, **kwargs): self.prerequisite_view.toolbar.view_selector.select('List View') row = self.prerequisite_view.entities.paginator.find_row_on_pages( - self.prerequisite_view.entities.elements, description=self.obj.name) - row.click() + self.prerequisite_view.entities.elements, hostname=self.obj.name) + row[0].check() + self.prerequisite_view.toolbar.policy.item_select('Edit Tags') -@navigator.register(ConfigSystem, 'All') -class SysAll(CFMENavigateStep): - VIEW = ConfigSystemAllView - prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn') +@navigator.register(ConfigProfile, 'Details') +class Details(CFMENavigateStep): + VIEW = ConfigManagementProfileView + prerequisite = NavigateToAttribute('manager', 'Details') def step(self, *args, **kwargs): - if hasattr(self.obj, 'type') and self.obj.type == 'Ansible Tower': - self.prerequisite_view.navigation.select('Automation', 'Ansible Tower', 'Explorer') - self.view.sidebar.configured_systems.tree.click_path( - 'All Ansible Tower Configured Systems') - else: - self.prerequisite_view.navigation.select('Configuration', 'Management') - self.view.sidebar.configured_systems.tree.click_path("All Configured Systems") + self.prerequisite_view.toolbar.view_selector.select('List View') + try: + row = self.prerequisite_view.entities.paginator.find_row_on_pages( + self.prerequisite_view.entities.elements, name=self.obj.name + ) + except NameError: + row = self.prerequisite_view.entities.paginator.find_row_on_pages( + self.prerequisite_view.entities.elements, description=self.obj.name + ) + row.click() -@navigator.register(ConfigSystem, 'EditTags') -class SysEditTags(CFMENavigateStep): - VIEW = TagPageView - prerequisite = NavigateToSibling('All') + +@navigator.register(ConfigManagerProviderCollection, "All") +class ConfigManagerAllPage(CFMENavigateStep): def step(self, *args, **kwargs): - self.prerequisite_view.toolbar.view_selector.select('List View') - row = self.prerequisite_view.entities.paginator.find_row_on_pages( - self.prerequisite_view.entities.elements, hostname=self.obj.name) - row[0].check() - self.prerequisite_view.toolbar.policy.item_select('Edit Tags') + raise NavigationDestinationNotFound( + "There is no page in MIQ that displays all config managers." + " Use 'AllOfType' on a config manager provider instance." + ) diff --git a/cfme/infrastructure/config_management/ansible_tower.py b/cfme/infrastructure/config_management/ansible_tower.py new file mode 100644 index 0000000000..ccd7ce0f1f --- /dev/null +++ b/cfme/infrastructure/config_management/ansible_tower.py @@ -0,0 +1,128 @@ +import attr +from navmazing import NavigateToAttribute +from widgetastic.widget import View + +from cfme.infrastructure.config_management import ConfigManagementCollectionView +from cfme.infrastructure.config_management import ConfigManagementEntities +from cfme.infrastructure.config_management import ConfigManagementSideBar +from cfme.infrastructure.config_management import ConfigManagementToolbar +from cfme.infrastructure.config_management import ConfigManagerProvider +from cfme.infrastructure.config_management.config_profiles import ConfigProfilesCollection +from cfme.infrastructure.config_management.config_systems import ConfigSystem +from cfme.utils import conf +from cfme.utils.appliance.implementations.ui import CFMENavigateStep +from cfme.utils.appliance.implementations.ui import navigator +from widgetastic_manageiq import Search + + +class AnsibleTowerProvidersAllView(ConfigManagementCollectionView): + """The main list view""" + toolbar = View.nested(ConfigManagementToolbar) + sidebar = View.nested(ConfigManagementSideBar) + search = View.nested(Search) + including_entities = View.include(ConfigManagementEntities, use_parent=True) + + @property + def is_displayed(self): + title_text = 'All Ansible Tower Providers' + return ( + self.in_config and + self.entities.title.text == title_text + ) + + +class ConfigSystemAllView(AnsibleTowerProvidersAllView): + """The config system view has a different title""" + + @property + def is_displayed(self): + title_text = 'All Ansible Tower Configured Systems' + return ( + self.in_config and + self.entities.title.text == title_text + ) + + +@attr.s(eq=False) +class AnsibleTowerProvider(ConfigManagerProvider): + """ + Configuration manager object (Ansible Tower) + + Args: + name: Name of the Ansible Tower configuration manager + url: URL, hostname or IP of the configuration 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: + Create provider: + .. code-block:: python + + tower_cfg_mgr = AnsibleTower('my_tower', 'https://my-tower.example.com/api/v1', + ssl=False, ConfigManager.Credential(principal='admin', + secret='testing'), key='tower_yaml_key') + tower_cfg_mgr.create() + + Update provider: + .. code-block:: python + + with update(tower_cfg_mgr): + tower_cfg_mgr.name = 'new_tower_name' + + Delete provider: + .. code-block:: python + + tower_cfg_mgr.delete() + """ + type_name = 'ansible_tower' + ui_type = 'Ansible Tower' + + _collections = { + "config_profiles": ConfigProfilesCollection + } + + @property + def ui_name(self): + """Return the name used in the UI""" + return '{} Automation Manager'.format(self.name) + + @classmethod + def from_config(cls, prov_config, prov_key, appliance=None): + """Returns 'ConfigManager' object loaded from yamls, based on its key""" + data = prov_config + creds = conf.credentials[data['credentials']] + return cls.appliance.collections.config_managers.instantiate( + cls, + name=data['name'], + url=data['url'], + credentials=cls.Credential( + principal=creds['username'], secret=creds['password']), + key=prov_key + ) + + +@navigator.register(AnsibleTowerProvider, 'AllOfType') +class MgrAll(CFMENavigateStep): + VIEW = AnsibleTowerProvidersAllView + prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn') + + def step(self, *args, **kwargs): + self.prerequisite_view.navigation.select('Automation', 'Ansible Tower', 'Explorer') + self.view.sidebar.providers.tree.click_path('All Ansible Tower Providers') + + def resetter(self, *args, **kwargs): + # Reset view and selection + self.view.toolbar.view_selector.select("List View") + + +@navigator.register(ConfigSystem, 'All') +class SysAll(CFMENavigateStep): + VIEW = ConfigSystemAllView + prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn') + + def step(self, *args, **kwargs): + self.prerequisite_view.navigation.select('Automation', 'Ansible Tower', 'Explorer') + self.view.sidebar.configured_systems.tree.click_path( + 'All Ansible Tower Configured Systems' + ) diff --git a/cfme/infrastructure/config_management/config_profiles.py b/cfme/infrastructure/config_management/config_profiles.py new file mode 100644 index 0000000000..4a0d57ba81 --- /dev/null +++ b/cfme/infrastructure/config_management/config_profiles.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +import attr + +from cfme.infrastructure.config_management.config_systems import ConfigSystemsCollection +from cfme.modeling.base import BaseCollection +from cfme.modeling.base import BaseEntity +from cfme.utils.appliance.implementations.ui import navigate_to +from cfme.utils.pretty import Pretty +from cfme.utils.wait import wait_for + + +@attr.s +class ConfigProfile(BaseEntity, Pretty): + """Configuration profile object (foreman-side hostgroup) + + Args: + name: Name of the profile + manager: ConfigManager object which this profile is bound to + """ + pretty_attrs = ['name', 'manager'] + + name = attr.ib() + manager = attr.ib() + + _collections = {"config_systems": ConfigSystemsCollection} + + @property + def type(self): + return ( + "Inventory Group" if self.manager.type == "ansible_tower" else "Configuration Profile" + ) + + @property + def config_systems(self): + """Returns 'ConfigSystem' objects that are active under this profile""" + return self.collections.config_systems.all() + + +@attr.s +class ConfigProfilesCollection(BaseCollection): + """ Collection of ConfigProfiles (nested collection of ConfigManager) """ + ENTITY = ConfigProfile + + def all(self): + """Returns 'ConfigProfile' configuration profiles (hostgroups) available on this manager""" + view = navigate_to(self.parent, "Details") + 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.parent.ui_type == 'Ansible Tower': + name = row.name.text + else: + name = row.description.text + if 'unassigned' in name.lower(): + continue + config_profiles.append(self.instantiate(name=name, manager=self.parent)) + return config_profiles diff --git a/cfme/infrastructure/config_management/config_systems/__init__.py b/cfme/infrastructure/config_management/config_systems/__init__.py new file mode 100644 index 0000000000..9cebdf81b9 --- /dev/null +++ b/cfme/infrastructure/config_management/config_systems/__init__.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +import attr + +from cfme.common import Taggable +from cfme.modeling.base import BaseCollection +from cfme.modeling.base import BaseEntity +from cfme.utils.appliance.implementations.ui import navigate_to +from cfme.utils.pretty import Pretty + + +@attr.s +class ConfigSystem(BaseEntity, Pretty, Taggable): + """The tags pages of the config system""" + pretty_attrs = ['name', 'manager_key'] + + name = attr.ib() + profile = attr.ib(default=None) + + def get_tags(self, tenant="My Company Tags"): + """Overridden get_tags method to deal with the fact that configured systems don't have a + details view.""" + view = navigate_to(self, 'EditTags') + return [ + self.appliance.collections.categories.instantiate( + display_name=r.category.text.replace('*', '').strip()).collections.tags.instantiate( + display_name=r.assigned_value.text.strip()) + for r in view.form.tags + ] + + +@attr.s +class ConfigSystemsCollection(BaseCollection): + """ Collection of ConfigSystem objects """ + ENTITY = ConfigSystem + + def all(self): + """Returns 'ConfigSystem' objects that are active under the system's profile""" + if self.filters: + view = navigate_to(self.parent, 'Details') + profile = self.parent + else: + view = navigate_to(self, "All") + profile = None + + view.toolbar.view_selector.select('List View') + # make sure all entities are displayed + view.entities.paginator.set_items_per_page(view.entities.paginator.items_amount) + + if view.entities.elements.is_displayed: + return [ + self.instantiate(row.hostname.text, profile) + for row in view.entities.elements + ] + else: + return [] diff --git a/cfme/infrastructure/config_management/config_systems/ansible_tower.py b/cfme/infrastructure/config_management/config_systems/ansible_tower.py new file mode 100644 index 0000000000..8c5ddeffcd --- /dev/null +++ b/cfme/infrastructure/config_management/config_systems/ansible_tower.py @@ -0,0 +1,14 @@ +import attr + +from cfme.infrastructure.config_management.config_systems import ConfigSystem +from cfme.infrastructure.config_management.config_systems import ConfigSystemsCollection + + +@attr.s +class AnsibleTowerSystem(ConfigSystem): + pass + + +@attr.s +class AnsibleTowerSystemsCollection(ConfigSystemsCollection): + ENTITY = AnsibleTowerSystem diff --git a/cfme/infrastructure/config_management/config_systems/satellite.py b/cfme/infrastructure/config_management/config_systems/satellite.py new file mode 100644 index 0000000000..e004ec349f --- /dev/null +++ b/cfme/infrastructure/config_management/config_systems/satellite.py @@ -0,0 +1,14 @@ +import attr + +from cfme.infrastructure.config_management.config_systems import ConfigSystem +from cfme.infrastructure.config_management.config_systems import ConfigSystemsCollection + + +@attr.s +class SatelliteSystem(ConfigSystem): + pass + + +@attr.s +class SatelliteSystemsCollection(ConfigSystemsCollection): + ENTITY = SatelliteSystem diff --git a/cfme/infrastructure/config_management/satellite.py b/cfme/infrastructure/config_management/satellite.py new file mode 100644 index 0000000000..db51507e9f --- /dev/null +++ b/cfme/infrastructure/config_management/satellite.py @@ -0,0 +1,129 @@ +import attr +from navmazing import NavigateToAttribute +from widgetastic.widget import View + +from cfme.infrastructure.config_management import ConfigManagementCollectionView +from cfme.infrastructure.config_management import ConfigManagementEntities +from cfme.infrastructure.config_management import ConfigManagementSideBar +from cfme.infrastructure.config_management import ConfigManagementToolbar +from cfme.infrastructure.config_management import ConfigManagerProvider +from cfme.infrastructure.config_management.config_profiles import ConfigProfilesCollection +from cfme.infrastructure.config_management.config_systems.satellite import SatelliteSystemsCollection # noqa +from cfme.utils import conf +from cfme.utils.appliance.implementations.ui import CFMENavigateStep +from cfme.utils.appliance.implementations.ui import navigator +from widgetastic_manageiq import Search + + +class SatelliteProvidersAllView(ConfigManagementCollectionView): + """The main list view""" + toolbar = View.nested(ConfigManagementToolbar) + sidebar = View.nested(ConfigManagementSideBar) + search = View.nested(Search) + including_entities = View.include(ConfigManagementEntities, use_parent=True) + + @property + def is_displayed(self): + """Is this view being displayed?""" + title_text = 'All Configuration Management Providers' + return ( + self.in_config and + self.entities.title.text == title_text + ) + + +class SatelliteSystemsAllView(SatelliteProvidersAllView): + """The config system view has a different title""" + + @property + def is_displayed(self): + title_text = 'All Configured Systems' + return ( + self.in_config and + self.entities.title.text == title_text + ) + + +@attr.s(eq=False) +class SatelliteProvider(ConfigManagerProvider): + """ + Configuration manager object (Red Hat Satellite, Foreman) + + Args: + name: Name of the Satellite/Foreman configuration manager + url: URL, hostname or IP of the configuration 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: + Create provider: + .. code-block:: python + + satellite_cfg_mgr = Satellite('my_satellite', 'my-satellite.example.com', + ssl=False, ConfigManager.Credential(principal='admin', + secret='testing'), key='satellite_yaml_key') + satellite_cfg_mgr.create() + + Update provider: + .. code-block:: python + + with update(satellite_cfg_mgr): + satellite_cfg_mgr.name = 'new_satellite_name' + + Delete provider: + .. code-block:: python + + satellite_cfg_mgr.delete() + """ + type_name = 'satellite' + ssl = attr.ib(default=None) + ui_type = 'Red Hat Satellite' + + _collections = { + "config_profiles": ConfigProfilesCollection + } + + @property + def ui_name(self): + """Return the name used in the UI""" + return '{} Configuration Manager'.format(self.name) + + @classmethod + def from_config(cls, prov_config, prov_key, appliance=None): + """Returns 'ConfigManager' object loaded from yamls, based on its key""" + data = prov_config + creds = conf.credentials[data['credentials']] + return cls.appliance.collections.config_managers.instantiate( + cls, + name=data['name'], + url=data['url'], + ssl=data['ssl'], + credentials=cls.Credential( + principal=creds['username'], secret=creds['password']), + key=prov_key + ) + + +@navigator.register(SatelliteProvider, 'AllOfType') +class MgrAll(CFMENavigateStep): + VIEW = SatelliteProvidersAllView + prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn') + + def step(self, *args, **kwargs): + self.prerequisite_view.navigation.select('Configuration', 'Management') + self.view.sidebar.providers.tree.click_path('All Configuration Manager Providers') + + def resetter(self, *args, **kwargs): + # Reset view and selection + self.view.toolbar.view_selector.select("List View") + + +@navigator.register(SatelliteSystemsCollection, 'All') +class SysAll(CFMENavigateStep): + VIEW = SatelliteSystemsAllView + prerequisite = NavigateToAttribute('appliance.server', 'LoggedIn') + + def step(self, *args, **kwargs): + self.prerequisite_view.navigation.select('Configuration', 'Management') + self.view.sidebar.configured_systems.tree.click_path("All Configured Systems") diff --git a/cfme/tests/infrastructure/test_config_management.py b/cfme/tests/infrastructure/test_config_management.py index ff1f3e417c..3625263ca8 100644 --- a/cfme/tests/infrastructure/test_config_management.py +++ b/cfme/tests/infrastructure/test_config_management.py @@ -3,13 +3,18 @@ from cfme import test_requirements from cfme.ansible_tower.explorer import TowerCreateServiceDialogFromTemplateView -from cfme.infrastructure.config_management import AnsibleTower -from cfme.utils.testgen import config_managers -from cfme.utils.testgen import generate +from cfme.infrastructure.config_management.ansible_tower import AnsibleTowerProvider +from cfme.infrastructure.config_management.satellite import SatelliteProvider +from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.update import update +from cfme.utils.wait import wait_for -pytest_generate_tests = generate(gen_func=config_managers) +pytestmark = [ + pytest.mark.provider([AnsibleTowerProvider, SatelliteProvider], scope='module'), + pytest.mark.usefixtures('setup_provider_modscope') +] + TEMPLATE_TYPE = { "job": "Job Template (Ansible Tower)", @@ -18,21 +23,14 @@ @pytest.fixture -def config_manager(config_manager_obj, appliance): - """ Fixture that provides a random config manager and sets it up""" - config_manager_obj.appliance = appliance - config_manager_obj.create() - yield config_manager_obj - config_manager_obj.delete() - - -@pytest.fixture -def config_system(config_manager): - return fauxfactory.gen_choice(config_manager.systems) +def config_system(provider): + # by selecting a profile we don't have to select fetch ALL the config systems on a provider + profile = provider.config_profiles[0] + return fauxfactory.gen_choice(profile.config_systems) @pytest.mark.tier(3) -def test_config_manager_detail_config_btn(request, config_manager): +def test_config_manager_detail_config_btn(provider): """ Polarion: assignee: nachandr @@ -40,23 +38,22 @@ def test_config_manager_detail_config_btn(request, config_manager): initialEstimate: 1/2h casecomponent: Ansible """ - config_manager.refresh_relationships() + provider.refresh_relationships() @pytest.mark.tier(2) -def test_config_manager_add(request, config_manager_obj): +def test_config_manager_add(provider): """ Polarion: assignee: nachandr casecomponent: Ansible initialEstimate: 1/4h """ - request.addfinalizer(config_manager_obj.delete) - config_manager_obj.create() + navigate_to(provider, "Details") @pytest.mark.tier(3) -def test_config_manager_add_invalid_url(request, config_manager_obj): +def test_config_manager_add_invalid_url(has_no_providers, provider): """ Polarion: assignee: nachandr @@ -64,15 +61,15 @@ def test_config_manager_add_invalid_url(request, config_manager_obj): initialEstimate: 1/15h casecomponent: Ansible """ - request.addfinalizer(config_manager_obj.delete) - config_manager_obj.url = 'https://invalid_url' + wait_for(lambda: not provider.exists, num_sec=60, delay=3) # wait for provider to be deleted + provider.url = 'https://invalid_url' error_message = 'getaddrinfo: Name or service not known' with pytest.raises(Exception, match=error_message): - config_manager_obj.create() + provider.create() @pytest.mark.tier(3) -def test_config_manager_add_invalid_creds(request, config_manager_obj): +def test_config_manager_add_invalid_creds(has_no_providers, provider): """ Polarion: assignee: nachandr @@ -80,19 +77,19 @@ def test_config_manager_add_invalid_creds(request, config_manager_obj): initialEstimate: 1/4h casecomponent: Ansible """ - request.addfinalizer(config_manager_obj.delete) - config_manager_obj.credentials.principal = 'invalid_user' - if config_manager_obj.type == "Ansible Tower": + wait_for(lambda: not provider.exists, num_sec=60, delay=3) # wait for provider to be deleted + provider.credentials.principal = 'invalid_user' + if provider.type == "ansible_tower": msg = ('validation was not successful: {"detail":"Authentication credentials ' 'were not provided. To establish a login session, visit /api/login/."}') else: msg = 'Credential validation was not successful: 401 Unauthorized' with pytest.raises(Exception, match=msg): - config_manager_obj.create() + provider.create() @pytest.mark.tier(3) -def test_config_manager_edit(request, config_manager): +def test_config_manager_edit(request, provider): """ Polarion: assignee: nachandr @@ -101,16 +98,16 @@ def test_config_manager_edit(request, config_manager): casecomponent: Ansible """ new_name = fauxfactory.gen_alpha(8) - old_name = config_manager.name - with update(config_manager): - config_manager.name = new_name - request.addfinalizer(lambda: config_manager.update(updates={'name': old_name})) - assert (config_manager.name == new_name and config_manager.exists),\ + old_name = provider.name + with update(provider): + provider.name = new_name + request.addfinalizer(lambda: provider.update(updates={'name': old_name})) + assert (provider.name == new_name and provider.exists),\ "Failed to update configuration manager's name" @pytest.mark.tier(3) -def test_config_manager_remove(config_manager): +def test_config_manager_remove(request, provider): """ Polarion: assignee: nachandr @@ -118,17 +115,12 @@ def test_config_manager_remove(config_manager): initialEstimate: 1/15h casecomponent: Ansible """ - config_manager.delete() + request.addfinalizer(provider.create) + provider.delete() -# Disable this test for Tower, no Configuration profiles can be retrieved from Tower side yet -# this is all real hackish because configmanager isn't a proper provider. @pytest.mark.tier(3) -@test_requirements.tag -@pytest.mark.uncollectif(lambda config_manager_obj: - isinstance(config_manager_obj, AnsibleTower), - reason='Ansible tower not valid for this test') -def test_config_system_tag(config_system, tag, appliance, config_manager, config_manager_obj): +def test_config_system_tag(config_system, tag): """ Polarion: assignee: anikifor @@ -140,11 +132,8 @@ def test_config_system_tag(config_system, tag, appliance, config_manager, config @pytest.mark.tier(3) -@test_requirements.tag -@pytest.mark.uncollectif(lambda config_manager_obj: - not isinstance(config_manager_obj, AnsibleTower), - reason='Only Ansible tower is valid for this test') -def test_ansible_tower_job_templates_tag(request, config_manager, tag, config_manager_obj): +@pytest.mark.provider([AnsibleTowerProvider], scope='module') +def test_ansible_tower_job_templates_tag(request, provider, tag): """ Polarion: assignee: anikifor @@ -156,7 +145,7 @@ def test_ansible_tower_job_templates_tag(request, config_manager, tag, config_ma 1673104 """ try: - job_template = config_manager.appliance.collections.ansible_tower_job_templates.all()[0] + job_template = provider.appliance.collections.ansible_tower_job_templates.all()[0] except IndexError: pytest.skip("No job template was found") job_template.add_tag(tag=tag, details=False) @@ -170,12 +159,9 @@ def test_ansible_tower_job_templates_tag(request, config_manager, tag, config_ma @pytest.mark.tier(3) -@pytest.mark.uncollectif(lambda config_manager_obj: - not isinstance(config_manager_obj, AnsibleTower), - reason='Only Ansible tower is valid for this test') +@pytest.mark.provider([AnsibleTowerProvider], scope='module') @pytest.mark.parametrize('template_type', TEMPLATE_TYPE.values(), ids=list(TEMPLATE_TYPE.keys())) -def test_ansible_tower_service_dialog_creation_from_template(config_manager, appliance, - template_type, config_manager_obj): +def test_ansible_tower_service_dialog_creation_from_template(provider, template_type): """ Polarion: assignee: nachandr @@ -185,7 +171,7 @@ def test_ansible_tower_service_dialog_creation_from_template(config_manager, app """ try: - job_template = config_manager.appliance.collections.ansible_tower_job_templates.filter( + job_template = provider.appliance.collections.ansible_tower_job_templates.filter( {"job_type": template_type}).all()[0] except IndexError: pytest.skip("No job template was found") diff --git a/cfme/tests/infrastructure/test_config_management_rest.py b/cfme/tests/infrastructure/test_config_management_rest.py index defceca32b..f4ff15274d 100644 --- a/cfme/tests/infrastructure/test_config_management_rest.py +++ b/cfme/tests/infrastructure/test_config_management_rest.py @@ -2,44 +2,26 @@ import pytest from cfme import test_requirements +from cfme.infrastructure.config_management.ansible_tower import AnsibleTowerProvider from cfme.utils.rest import assert_response from cfme.utils.rest import delete_resources_from_detail from cfme.utils.rest import query_resource_attributes -from cfme.utils.testgen import config_managers -from cfme.utils.testgen import generate from cfme.utils.wait import wait_for -pytest_generate_tests = generate( - # config_managers generates list of managers from cfme_data - gen_func=config_managers, - # Filter the config manager types for Tower - managers_type='ansible', - scope='module' -) -pytestmark = [test_requirements.rest] - - -@pytest.fixture(scope='module') -def config_manager(config_manager_obj, appliance): - """Fixture that provides a random config manager and sets it up.""" - config_manager_obj.appliance = appliance - if config_manager_obj.type != 'Ansible Tower': - pytest.skip('A non "Ansible Tower" configuration manager provider was selected for test,' - 'Please verify cfme_data.yaml content.') - - config_manager_obj.create(validate=True) # actually add it to CFME - yield config_manager_obj - - config_manager_obj.delete() +pytestmark = [ + test_requirements.rest, + pytest.mark.provider([AnsibleTowerProvider], scope='module'), + pytest.mark.usefixtures('setup_provider') +] @pytest.fixture -def authentications(appliance, config_manager): +def authentications(appliance, provider): """Creates and returns authentication resources under /api/authentications.""" auth_num = 2 collection = appliance.rest_api.collections.authentications - prov = appliance.rest_api.collections.providers.get(name='{} %'.format(config_manager.name)) + prov = appliance.rest_api.collections.providers.get(name='{} %'.format(provider.name)) data = [] cred_names = [] for __ in range(auth_num): @@ -216,7 +198,7 @@ def test_delete_authentications_from_collection(self, appliance, authentications appliance.rest_api.collections.authentications.action.delete.POST(*authentications) assert_response(appliance, success=False) - def test_authentications_options(self, appliance, config_manager): + def test_authentications_options(self, appliance): """Tests that credential types can be listed through OPTIONS HTTP method. Metadata: @@ -233,12 +215,16 @@ def test_authentications_options(self, appliance, config_manager): assert_response(appliance) -@pytest.fixture(scope="module") -def config_manager_rest(config_manager_obj): +@pytest.fixture +def config_manager_rest(provider): """Creates provider using REST API.""" - config_manager_obj.create_rest() - assert_response(config_manager_obj.appliance) - rest_entity = config_manager_obj.rest_api_entity + # first remove the provider + provider.delete() + assert not provider.exists + # now create the provider via rest + provider.create_rest() + assert_response(provider.appliance) + rest_entity = provider.rest_api_entity rest_entity.reload() yield rest_entity diff --git a/cfme/tests/services/test_ansible_workflow_servicecatalogs.py b/cfme/tests/services/test_ansible_workflow_servicecatalogs.py index 7b23d9f51d..c71ca6773e 100644 --- a/cfme/tests/services/test_ansible_workflow_servicecatalogs.py +++ b/cfme/tests/services/test_ansible_workflow_servicecatalogs.py @@ -1,9 +1,9 @@ import pytest from cfme import test_requirements +from cfme.infrastructure.config_management.ansible_tower import AnsibleTowerProvider from cfme.services.myservice import MyService from cfme.services.service_catalogs import ServiceCatalogs -from cfme.utils import testgen from cfme.utils.blockers import BZ from cfme.utils.log import logger @@ -11,6 +11,8 @@ pytestmark = [ test_requirements.service, pytest.mark.tier(2), + pytest.mark.provider([AnsibleTowerProvider], scope='module'), + pytest.mark.usefixtures('setup_provider'), pytest.mark.parametrize('workflow_type', ['multiple_job_workflow', 'inventory_sync_workflow'], ids=['multiple_job_workflow', 'inventory_sync_workflow'], scope='module'), @@ -18,39 +20,12 @@ ] -def pytest_generate_tests(metafunc): - # Filter out providers without provisioning data or hosts defined - argnames, argvalues, idlist = testgen.config_managers(metafunc) - new_idlist = [] - new_argvalues = [] - for i, argvalue_tuple in enumerate(argvalues): - args = dict(zip(argnames, argvalue_tuple)) - - if not args['config_manager_obj'].yaml_data['provisioning']: - continue - - new_idlist.append(idlist[i]) - new_argvalues.append(argvalues[i]) - - testgen.parametrize(metafunc, argnames, new_argvalues, ids=new_idlist, scope='module') - - -@pytest.fixture(scope="module") -def tower_manager(config_manager_obj, appliance): - """ Fixture that sets up Ansible Tower provider""" - config_manager_obj.appliance = appliance - if config_manager_obj.type == "Ansible Tower": - config_manager_obj.create(validate=True) - yield config_manager_obj - config_manager_obj.delete() - - @pytest.fixture(scope="function") -def ansible_workflow_catitem(appliance, request, tower_manager, dialog, catalog, workflow_type): - config_manager_obj = tower_manager - provider_name = config_manager_obj.yaml_data.get('name') +def ansible_workflow_catitem(appliance, provider, dialog, catalog, workflow_type): + config_manager_obj = provider + provider_name = config_manager_obj.data.get('name') try: - template = config_manager_obj.yaml_data['provisioning_data'][workflow_type] + template = config_manager_obj.data['provisioning_data'][workflow_type] except KeyError: pytest.skip("No such Ansible template: {} found in cfme_data.yaml".format(workflow_type)) catalog_item = appliance.collections.catalog_items.create( @@ -67,8 +42,7 @@ def ansible_workflow_catitem(appliance, request, tower_manager, dialog, catalog, @pytest.mark.meta(automates=[BZ(1719051)]) -def test_tower_workflow_item(appliance, tower_manager, ansible_workflow_catitem, request, - workflow_type): +def test_tower_workflow_item(appliance, ansible_workflow_catitem): """Tests ordering of catalog items for Ansible Workflow templates Metadata: test_flag: provision @@ -94,7 +68,7 @@ def test_tower_workflow_item(appliance, tower_manager, ansible_workflow_catitem, ) -def test_retire_ansible_workflow(appliance, ansible_workflow_catitem, request, workflow_type): +def test_retire_ansible_workflow(appliance, ansible_workflow_catitem): """Tests retiring of catalog items for Ansible Workflow templates Metadata: test_flag: provision diff --git a/cfme/tests/services/test_config_provider_servicecatalogs.py b/cfme/tests/services/test_config_provider_servicecatalogs.py index 28f21d35b9..fceefcf6a4 100644 --- a/cfme/tests/services/test_config_provider_servicecatalogs.py +++ b/cfme/tests/services/test_config_provider_servicecatalogs.py @@ -1,9 +1,9 @@ import pytest from cfme import test_requirements +from cfme.infrastructure.config_management.ansible_tower import AnsibleTowerProvider from cfme.services.myservice import MyService from cfme.services.service_catalogs import ServiceCatalogs -from cfme.utils import testgen from cfme.utils.blockers import BZ from cfme.utils.blockers import GH from cfme.utils.log import logger @@ -11,6 +11,8 @@ pytestmark = [ test_requirements.service, + pytest.mark.provider([AnsibleTowerProvider], scope='module'), + pytest.mark.usefixtures('setup_provider'), pytest.mark.tier(2), pytest.mark.parametrize('job_type', ['template', 'template_limit', 'template_survey', 'textarea_survey'], @@ -20,40 +22,11 @@ ] -def pytest_generate_tests(metafunc): - # Filter out providers without provisioning data or hosts defined - argnames, argvalues, idlist = testgen.config_managers(metafunc) - new_idlist = [] - new_argvalues = [] - for i, argvalue_tuple in enumerate(argvalues): - args = dict(list(zip(argnames, argvalue_tuple))) - - if not args['config_manager_obj'].yaml_data['provisioning']: - continue - - new_idlist.append(idlist[i]) - new_argvalues.append(argvalues[i]) - - testgen.parametrize(metafunc, argnames, new_argvalues, ids=new_idlist, scope='module') - - -@pytest.fixture(scope="module") -def config_manager(config_manager_obj, appliance): - """ Fixture that provides a random config manager and sets it up""" - config_manager_obj.appliance = appliance - if config_manager_obj.type == "Ansible Tower": - config_manager_obj.create(validate=True) - else: - config_manager_obj.create() - yield config_manager_obj - config_manager_obj.delete() - - @pytest.fixture(scope="function") -def catalog_item(appliance, request, config_manager, ansible_tower_dialog, catalog, job_type): - config_manager_obj = config_manager - provider_name = config_manager_obj.yaml_data.get('name') - template = config_manager_obj.yaml_data['provisioning_data'][job_type] +def catalog_item(appliance, request, provider, ansible_tower_dialog, catalog, job_type): + config_manager_obj = provider + provider_name = config_manager_obj.data.get('name') + template = config_manager_obj.data['provisioning_data'][job_type] catalog_item = appliance.collections.catalog_items.create( appliance.collections.catalog_items.ANSIBLE_TOWER, name=ansible_tower_dialog.label, @@ -69,7 +42,7 @@ def catalog_item(appliance, request, config_manager, ansible_tower_dialog, catal @pytest.mark.meta(automates=[BZ(1717500)]) # The 'textarea_survey' job type automates BZ 1717500 -def test_order_tower_catalog_item(appliance, config_manager, catalog_item, request, job_type): +def test_order_tower_catalog_item(appliance, provider, catalog_item, request, job_type): """Tests ordering of catalog items for Ansible Template and Workflow jobs Metadata: test_flag: provision @@ -84,7 +57,7 @@ def test_order_tower_catalog_item(appliance, config_manager, catalog_item, reque caseimportance: high """ if job_type == 'template_limit': - host = config_manager.yaml_data['provisioning_data']['inventory_host'] + host = provider.data['provisioning_data']['inventory_host'] dialog_values = {'limit': host} service_catalogs = ServiceCatalogs(appliance, catalog_item.catalog, catalog_item.name, dialog_values=dialog_values) diff --git a/cfme/tests/webui/test_advanced_search.py b/cfme/tests/webui/test_advanced_search.py index d5b53fde5e..d06f39c552 100644 --- a/cfme/tests/webui/test_advanced_search.py +++ b/cfme/tests/webui/test_advanced_search.py @@ -7,7 +7,7 @@ from cfme import test_requirements from cfme.cloud.provider import CloudProvider from cfme.containers.provider import ContainersProvider -from cfme.infrastructure.config_management import ConfigManager +from cfme.infrastructure.config_management import ConfigManagerProvider from cfme.infrastructure.config_management import ConfigSystem from cfme.infrastructure.provider import InfraProvider from cfme.markers.env_markers.provider import ONE_PER_CATEGORY @@ -17,13 +17,14 @@ from cfme.services.workloads import VmsInstances from cfme.utils.appliance.implementations.ui import navigate_to from cfme.utils.blockers import BZ +from cfme.utils.blockers import GH SearchParam = namedtuple("SearchParam", ["collection", "destination", "entity", "filter", "my_filters"]) pytestmark = [ pytest.mark.uncollectif(lambda param, appliance: - (param.collection in [ConfigManager, 'ansible_tower_providers'] or + (param.collection in [ConfigManagerProvider, 'config_managers'] or param.filter == 'Job Template (Ansible Tower) : Name') or (appliance.version >= '5.11' and param.entity == 'network_load_balancers'), reason='load balancers are no longer supported in 5.11 -> BZ 1672949'), @@ -296,11 +297,13 @@ class TestContainers(object): @inject_tests +@pytest.mark.meta(blockers=[GH('ManageIQ/integration_tests:9723')]) class TestAnsibleTower(object): params_values = [ SearchParam('ansible_tower_providers', 'All', 'ansible_tower_explorer_provider', 'Automation Manager (Ansible Tower) : Name', ('sidebar.providers', 'All Ansible Tower Providers')), + SearchParam('ansible_tower_systems', 'All', 'ansible_tower_explorer_system', 'Configured System (Ansible Tower) : Hostname', ('sidebar.configured_systems', 'All Ansible Tower Configured Systems')), @@ -329,9 +332,10 @@ class TestStorage(object): @inject_tests +@pytest.mark.meta(blockers=[GH('ManageIQ/integration_tests:9723')]) class TestConfigManagement(object): params_values = [ - SearchParam(ConfigManager, 'All', 'configuration_management', + SearchParam(ConfigManagerProvider, 'All', 'configuration_management', 'Configuration Manager : Name', ('sidebar.providers', "All Configuration Management Providers")), SearchParam(ConfigSystem, 'All', 'configuration_management_systems', diff --git a/cfme/utils/appliance/__init__.py b/cfme/utils/appliance/__init__.py index b4d992a821..70206d69d7 100644 --- a/cfme/utils/appliance/__init__.py +++ b/cfme/utils/appliance/__init__.py @@ -60,7 +60,8 @@ RUNNING_UNDER_SPROUT = os.environ.get("RUNNING_UNDER_SPROUT", "false") != "false" # EMS types recognized by IP or credentials RECOGNIZED_BY_IP = [ - "InfraManager", "ContainerManager", "Openstack::CloudManager" + "InfraManager", "ContainerManager", "Openstack::CloudManager", "ConfigurationManager", + "AutomationManager" ] RECOGNIZED_BY_CREDS = ["CloudManager", "Nuage::NetworkManager"] @@ -612,6 +613,9 @@ def managed_known_providers(self): if ems_name == prov.name: found_cruds.add(prov) break + elif prov.name in ems_name: # config managers append e.g. 'Automation Manager' + found_cruds.add(prov) + break else: unrecognized_ems_names.add(ems_name) if unrecognized_ems_names: diff --git a/cfme/utils/testgen.py b/cfme/utils/testgen.py index 5ec3a42687..64eb4e8134 100644 --- a/cfme/utils/testgen.py +++ b/cfme/utils/testgen.py @@ -87,7 +87,6 @@ def gen_providers: import pytest from cfme.common.provider import BaseProvider -from cfme.infrastructure.config_management import get_config_manager_from_config from cfme.roles import group_data from cfme.utils.conf import auth_data from cfme.utils.conf import cfme_data @@ -317,26 +316,6 @@ def auth_groups(metafunc, auth_mode): return argnames, argvalues, idlist -def config_managers(metafunc, managers_type=None): - """Provides config managers - """ - argnames = ['config_manager_obj'] - argvalues = [] - idlist = [] - - data = cfme_data.get('configuration_managers', {}) - - for cfg_mgr_key in data: - config_mgr = get_config_manager_from_config(cfg_mgr_key, - appliance=None, - mgr_type=managers_type) - if config_mgr is None: - continue # type mismatch - argvalues.append([config_mgr]) - idlist.append(cfg_mgr_key) - return argnames, argvalues, idlist - - def pxe_servers(metafunc): """Provides pxe data """ diff --git a/conf/supportability.yaml b/conf/supportability.yaml index ce626c1f4d..89c82ba1bc 100644 --- a/conf/supportability.yaml +++ b/conf/supportability.yaml @@ -31,3 +31,8 @@ - 3.7 - 3.9 - 3.11 + config_manager: + - satellite: + - 6.2 + - ansible_tower: + - 3.10 diff --git a/entry_points.txt b/entry_points.txt index 9b74292934..6a1f2b9092 100644 --- a/entry_points.txt +++ b/entry_points.txt @@ -16,8 +16,7 @@ ansible_playbooks = cfme.ansible.playbooks:PlaybooksCollection ansible_repositories = cfme.ansible.repositories:RepositoryCollection ansible_tower_job_templates = cfme.ansible_tower.explorer:AnsibleTowerJobTemplatesCollection ansible_tower_jobs = cfme.ansible_tower.jobs:TowerJobsCollection -ansible_tower_providers = cfme.ansible_tower.explorer:AnsibleTowerProvidersCollection -ansible_tower_systems = cfme.ansible_tower.explorer:AnsibleTowerSystemsCollection +ansible_tower_systems = cfme.infrastructure.config_management.config_systems.ansible_tower:AnsibleTowerSystemsCollection automate_import_exports = cfme.automate.import_export:AutomateImportExportsCollection balancers = cfme.networks.balancer:BalancerCollection block_managers = cfme.storage.manager:BlockManagerCollection @@ -42,6 +41,7 @@ cloud_tenants = cfme.cloud.tenant:TenantCollection clusters = cfme.infrastructure.cluster:ClusterCollection compute_rates = cfme.intelligence.chargeback.rates:ComputeRateCollection conditions = cfme.control.explorer.conditions:ConditionCollection +config_managers = cfme.infrastructure.config_management:ConfigManagerProviderCollection container_builds = cfme.containers.build:BuildCollection container_image_registries = cfme.containers.image_registry:ImageRegistryCollection container_images = cfme.containers.image:ImageCollection @@ -105,6 +105,7 @@ reports = cfme.intelligence.reports.reports:ReportsCollection requests = cfme.services.requests:RequestCollection resource_pools = cfme.infrastructure.resource_pool:ResourcePoolCollection roles = cfme.configure.access_control:RoleCollection +satellite_systems = cfme.infrastructure.config_management.config_systems.satellite:SatelliteSystemsCollection saved_reports = cfme.intelligence.reports.saved:SavedReportsCollection schedules = cfme.intelligence.reports.schedules:ScheduleCollection security_groups = cfme.cloud.security_groups:SecurityGroupCollection @@ -139,6 +140,7 @@ saml = cfme.utils.auth:SAMLAuthProvider [manageiq.provider_categories] cloud = cfme.cloud.provider:CloudProvider +config_manager = cfme.infrastructure.config_management:ConfigManagerProvider containers = cfme.containers.provider:ContainersProvider infra = cfme.infrastructure.provider:InfraProvider networks = cfme.networks.provider:NetworkProvider @@ -161,6 +163,10 @@ rhevm = cfme.infrastructure.provider.rhevm:RHEVMProvider scvmm = cfme.infrastructure.provider.scvmm:SCVMMProvider virtualcenter = cfme.infrastructure.provider.virtualcenter:VMwareProvider +[manageiq.provider_types.config_manager] +ansible_tower = cfme.infrastructure.config_management.ansible_tower:AnsibleTowerProvider +satellite = cfme.infrastructure.config_management.satellite:SatelliteProvider + [manageiq.provider_types.networks] nuage = cfme.networks.provider.nuage:NuageProvider