diff --git a/conf/capsule.yaml.template b/conf/capsule.yaml.template index 9ef9e48a2c2..b618bf57ea9 100644 --- a/conf/capsule.yaml.template +++ b/conf/capsule.yaml.template @@ -1,4 +1,6 @@ CAPSULE: + # Capsule hostname for N-minus testing + HOSTNAME: VERSION: # The full release version (6.9.2) RELEASE: # populate with capsule version diff --git a/conf/dynaconf_hooks.py b/conf/dynaconf_hooks.py index 6d09d6e5bec..610f0ba4c4d 100644 --- a/conf/dynaconf_hooks.py +++ b/conf/dynaconf_hooks.py @@ -85,9 +85,9 @@ def get_ohsnap_repos(settings): settings, repo='capsule', product='capsule', - release=settings.server.version.release, - os_release=settings.server.version.rhel_version, - snap=settings.server.version.snap, + release=settings.capsule.version.release, + os_release=settings.capsule.version.rhel_version, + snap=settings.capsule.version.snap, ) data['SATELLITE_REPO'] = get_ohsnap_repo_url( diff --git a/conftest.py b/conftest.py index 92ae19adc8b..1322196afab 100644 --- a/conftest.py +++ b/conftest.py @@ -21,6 +21,7 @@ 'pytest_plugins.requirements.update_requirements', 'pytest_plugins.sanity_plugin', 'pytest_plugins.video_cleanup', + 'pytest_plugins.capsule_n-minus', # Fixtures 'pytest_fixtures.core.broker', 'pytest_fixtures.core.sat_cap_factory', diff --git a/pytest_fixtures/core/sat_cap_factory.py b/pytest_fixtures/core/sat_cap_factory.py index b6c923d7f3d..cdadad9f141 100644 --- a/pytest_fixtures/core/sat_cap_factory.py +++ b/pytest_fixtures/core/sat_cap_factory.py @@ -1,4 +1,5 @@ from contextlib import contextmanager +from functools import lru_cache from broker import Broker from packaging.version import Version @@ -37,13 +38,29 @@ def _target_satellite_host(request, satellite_factory): yield +@lru_cache +def cached_capsule_cdn_register(hostname=None): + cap = Capsule.get_host_by_hostname(hostname=hostname) + cap.enable_capsule_downstream_repos() + + @contextmanager def _target_capsule_host(request, capsule_factory): - if 'sanity' not in request.config.option.markexpr: + if 'sanity' not in request.config.option.markexpr and not request.config.option.n_minus: new_cap = capsule_factory() yield new_cap new_cap.teardown() Broker(hosts=[new_cap]).checkin() + elif request.config.option.n_minus: + if not settings.capsule.hostname: + hosts = Capsule.get_hosts_from_inventory(filter="'cap' in @inv.name") + settings.capsule.hostname = hosts[0].hostname + cap = hosts[0] + else: + cap = Capsule.get_host_by_hostname(settings.capsule.hostname) + # Capsule needs RHEL contents for some tests + cached_capsule_cdn_register(hostname=settings.capsule.hostname) + yield cap else: yield @@ -148,9 +165,10 @@ def session_capsule_host(request, capsule_factory): @pytest.fixture -def capsule_configured(capsule_host, target_sat): +def capsule_configured(request, capsule_host, target_sat): """Configure the capsule instance with the satellite from settings.server.hostname""" - capsule_host.capsule_setup(sat_host=target_sat) + if not request.config.option.n_minus: + capsule_host.capsule_setup(sat_host=target_sat) return capsule_host @@ -162,21 +180,23 @@ def large_capsule_configured(large_capsule_host, target_sat): @pytest.fixture(scope='module') -def module_capsule_configured(module_capsule_host, module_target_sat): +def module_capsule_configured(request, module_capsule_host, module_target_sat): """Configure the capsule instance with the satellite from settings.server.hostname""" - module_capsule_host.capsule_setup(sat_host=module_target_sat) + if not request.config.option.n_minus: + module_capsule_host.capsule_setup(sat_host=module_target_sat) return module_capsule_host @pytest.fixture(scope='session') -def session_capsule_configured(session_capsule_host, session_target_sat): +def session_capsule_configured(request, session_capsule_host, session_target_sat): """Configure the capsule instance with the satellite from settings.server.hostname""" - session_capsule_host.capsule_setup(sat_host=session_target_sat) + if not request.config.option.n_minus: + session_capsule_host.capsule_setup(sat_host=session_target_sat) return session_capsule_host @pytest.fixture(scope='module') -def module_capsule_configured_mqtt(module_capsule_configured): +def module_capsule_configured_mqtt(request, module_capsule_configured): """Configure the capsule instance with the satellite from settings.server.hostname, enable MQTT broker""" module_capsule_configured.set_rex_script_mode_provider('pull-mqtt') @@ -192,7 +212,9 @@ def module_capsule_configured_mqtt(module_capsule_configured): ) result = module_capsule_configured.cli.Service.restart(options={'only': 'foreman-proxy'}) assert result.status == 0, 'foreman-proxy restart unsuccessful' - return module_capsule_configured + yield module_capsule_configured + if request.config.option.n_minus: + raise TypeError('The teardown is missed for MQTT configuration undo for nminus testing') @pytest.fixture(scope='module') diff --git a/pytest_plugins/capsule_n-minus.py b/pytest_plugins/capsule_n-minus.py new file mode 100644 index 00000000000..f903e239757 --- /dev/null +++ b/pytest_plugins/capsule_n-minus.py @@ -0,0 +1,55 @@ +# Collection of Capsule Factory fixture tests +# No destructive tests +# Adjust capsule host and capsule_configured host behavior for n_minus testing +# Calculate capsule hostname from inventory just as we do in xDist.py +from robottelo.config import settings +from robottelo.hosts import Capsule + + +def pytest_addoption(parser): + """Add options for pytest to collect tests based on fixtures its using""" + help_text = ''' + Collects tests based on capsule fixtures used by tests and uncollect destructive tests + + Usage: --n-minus + + example: pytest --n-minus tests/foreman + ''' + parser.addoption("--n-minus", action='store_true', default=False, help=help_text) + + +def pytest_collection_modifyitems(items, config): + + if not config.getoption('n_minus', False): + return + + selected = [] + deselected = [] + + for item in items: + is_destructive = item.get_closest_marker('destructive') + # Deselect Destructive tests and tests without capsule_factory fixture + if 'capsule_factory' not in item.fixturenames or is_destructive: + deselected.append(item) + continue + # Ignoring all puppet tests as they are destructive in nature + # and needs its own satellite for verification + if 'session_puppet_enabled_sat' in item.fixturenames: + deselected.append(item) + continue + # Ignoring all satellite maintain tests as they are destructive in nature + # Also dont need them in nminus testing as its not integration testing + if 'sat_maintain' in item.fixturenames and 'satellite' in item.callspec.params.values(): + deselected.append(item) + continue + selected.append(item) + + config.hook.pytest_deselected(items=deselected) + items[:] = selected + + +def pytest_sessionfinish(session, exitstatus): + # Unregister the capsule from CDN after all tests + if session.config.option.n_minus and not session.config.option.collectonly: + caps = Capsule.get_host_by_hostname(hostname=settings.capsule.hostname) + caps.unregister() diff --git a/robottelo/exceptions.py b/robottelo/exceptions.py index 83022dfcd6e..a6100564873 100644 --- a/robottelo/exceptions.py +++ b/robottelo/exceptions.py @@ -75,6 +75,12 @@ class CLIError(Exception): """Indicates that a CLI command could not be run.""" +class CapsuleHostError(Exception): + """Indicates error in capsule configuration etc""" + + pass + + class CLIBaseError(Exception): """Indicates that a CLI command has finished with return code different from zero. diff --git a/robottelo/hosts.py b/robottelo/hosts.py index 1fc2e433830..2c9cd172e34 100644 --- a/robottelo/hosts.py +++ b/robottelo/hosts.py @@ -1555,6 +1555,21 @@ def get_features(self): """Get capsule features""" return requests.get(f'https://{self.hostname}:9090/features', verify=False).text + def enable_capsule_downstream_repos(self): + """Enable CDN repos and capsule downstream repos on Capsule Host""" + # CDN Repos + self.register_to_cdn() + for repo in getattr(constants, f"OHSNAP_RHEL{self.os_version.major}_REPOS"): + result = self.enable_repo(repo, force=True) + if result.status: + raise CapsuleHostError(f'Repo enable at capsule host failed\n{result.stdout}') + # Downstream Capsule specific Repos + self.download_repofile( + product='capsule', + release=settings.capsule.version.release, + snap=settings.capsule.version.snap, + ) + def capsule_setup(self, sat_host=None, **installer_kwargs): """Prepare the host and run the capsule installer""" self._satellite = sat_host or Satellite()