From 029cc8371efbec52d75a593b77d03f260f6bac42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 23:38:15 -0400 Subject: [PATCH 01/31] Bump requests from 2.32.2 to 2.32.3 (#15222) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f4b9e00f413..ac368270c74 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ pytest-xdist==3.6.1 pytest-fixturecollection==0.1.2 pytest-ibutsu==2.2.4 PyYAML==6.0.1 -requests==2.32.2 +requests==2.32.3 tenacity==8.3.0 testimony==2.4.0 wait-for==1.2.0 From d88e9c05889eb13ec04f60caad617e1364d69396 Mon Sep 17 00:00:00 2001 From: David Moore <109112035+damoore044@users.noreply.github.com> Date: Thu, 30 May 2024 03:12:57 -0400 Subject: [PATCH 02/31] ContentCredentials CLI fix for stream (#15165) --- tests/foreman/cli/test_contentcredentials.py | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/foreman/cli/test_contentcredentials.py b/tests/foreman/cli/test_contentcredentials.py index 14d4ff50718..c80851b2b8f 100644 --- a/tests/foreman/cli/test_contentcredentials.py +++ b/tests/foreman/cli/test_contentcredentials.py @@ -45,6 +45,27 @@ def create_gpg_key_file(content=None): return None +def wait_for_repo_metadata_tasks(sat, org_name, repo_name='', product_name=''): + """Search and wait for any repository metadata generate task(s). + + param sat (satellite): is required. + param org_name (str): is required, rest are optional. + Scope by use of repo/product name, if desired. + + return: the matching completed task(s) + """ + task_query = ( + f'Metadata generate repository "{repo_name}";' + f' product "{product_name}";' + f' organization "{org_name}"' + ) + return sat.wait_for_tasks( + search_query=task_query, + search_rate=15, + max_tries=10, + ) + + search_key = 'name' @@ -403,6 +424,13 @@ def test_positive_add_product_with_repo(target_sat, module_org): target_sat.cli.Product.update( {'gpg-key-id': gpg_key['id'], 'id': product['id'], 'organization-id': module_org.id} ) + # wait for repo metadata task(s) before final read/assertions + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + repo_name=repo['name'], + product_name=product['name'], + ) product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) repo = target_sat.cli.Repository.info({'id': repo['id']}) assert product['gpg']['gpg-key-id'] == gpg_key['id'] @@ -429,6 +457,11 @@ def test_positive_add_product_with_repos(target_sat, module_org): target_sat.cli.Product.update( {'gpg-key-id': gpg_key['id'], 'id': product['id'], 'organization-id': module_org.id} ) + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + product_name=product['name'], + ) product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) assert product['gpg']['gpg-key-id'] == gpg_key['id'] for repo in repos: @@ -455,6 +488,12 @@ def test_positive_add_repo_from_product_with_repo(target_sat, module_org): target_sat.cli.Repository.update( {'gpg-key-id': gpg_key['id'], 'id': repo['id'], 'organization-id': module_org.id} ) + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + repo_name=repo['name'], + product_name=product['name'], + ) product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) repo = target_sat.cli.Repository.info({'id': repo['id']}) assert repo['gpg-key']['id'] == gpg_key['id'] @@ -482,6 +521,11 @@ def test_positive_add_repo_from_product_with_repos(target_sat, module_org): target_sat.cli.Repository.update( {'gpg-key-id': gpg_key['id'], 'id': repos[0]['id'], 'organization-id': module_org.id} ) + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + product_name=product['name'], + ) product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) assert product['gpg'].get('gpg-key-id') != gpg_key['id'] # First repo should have a valid gpg key assigned @@ -566,6 +610,13 @@ def test_positive_update_key_for_product_with_repo(target_sat, module_org): {'id': gpg_key['id'], 'organization-id': module_org.id} ) assert gpg_key['name'] == new_name + # wait for repo metadata task(s) before final read/assertions + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + repo_name=repo['name'], + product_name=product['name'], + ) # Verify changes are reflected in the product product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) assert product['gpg']['gpg-key'] == new_name @@ -601,6 +652,12 @@ def test_positive_update_key_for_product_with_repos(target_sat, module_org): product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) assert product['gpg']['gpg-key'] == gpg_key['name'] for repo in repos: + # one metadata task per repo associated w/ GPG + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + product_name=product['name'], + ) repo = target_sat.cli.Repository.info({'id': repo['id']}) assert repo['gpg-key'].get('name') == gpg_key['name'] # Update the gpg key @@ -616,6 +673,11 @@ def test_positive_update_key_for_product_with_repos(target_sat, module_org): # Verify changes are reflected in the product product = target_sat.cli.Product.info({'id': product['id'], 'organization-id': module_org.id}) assert product['gpg']['gpg-key'] == new_name + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + product_name=product['name'], + ) # Verify changes are reflected in the repositories for repo in repos: repo = target_sat.cli.Repository.info({'id': repo['id']}) @@ -652,6 +714,12 @@ def test_positive_update_key_for_repo_from_product_with_repo(target_sat, module_ {'id': gpg_key['id'], 'organization-id': module_org.id} ) assert gpg_key['name'] == new_name + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + repo_name=repo['name'], + product_name=product['name'], + ) # Verify changes are reflected in the repositories repo = target_sat.cli.Repository.info({'id': repo['id']}) assert repo['gpg-key'].get('name') == new_name @@ -697,6 +765,12 @@ def test_positive_update_key_for_repo_from_product_with_repos(target_sat, module {'id': gpg_key['id'], 'organization-id': module_org.id} ) assert gpg_key['name'] == new_name + wait_for_repo_metadata_tasks( + sat=target_sat, + org_name=module_org.name, + repo_name=repos[0]['name'], + product_name=product['name'], + ) # Verify changes are reflected in the associated repository repos[0] = target_sat.cli.Repository.info({'id': repos[0]['id']}) assert repos[0]['gpg-key'].get('name') == new_name From 3fa1539607ed28a7362bcf30359bafcb4c537411 Mon Sep 17 00:00:00 2001 From: Shweta Singh Date: Thu, 30 May 2024 16:34:11 +0530 Subject: [PATCH 03/31] Add marker for case insensitive test collection by component and importance (#15005) Collect tests with case insensitive component marker --- pytest_plugins/metadata_markers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pytest_plugins/metadata_markers.py b/pytest_plugins/metadata_markers.py index 59b1e6c9e56..1e04182279d 100644 --- a/pytest_plugins/metadata_markers.py +++ b/pytest_plugins/metadata_markers.py @@ -163,8 +163,8 @@ def pytest_collection_modifyitems(items, config): # split the option string and handle no option, single option, multiple # config.getoption(default) doesn't work like you think it does, hence or '' - importance = [i for i in (config.getoption('importance') or '').split(',') if i != ''] - component = [c for c in (config.getoption('component') or '').split(',') if c != ''] + importance = [i.lower() for i in (config.getoption('importance') or '').split(',') if i != ''] + component = [c.lower() for c in (config.getoption('component') or '').split(',') if c != ''] team = [a.lower() for a in (config.getoption('team') or '').split(',') if a != ''] verifies_issues = config.getoption('verifies_issues') blocked_by = config.getoption('blocked_by') @@ -192,10 +192,10 @@ def pytest_collection_modifyitems(items, config): # only add the mark if it hasn't already been applied at a lower scope doc_component = component_regex.findall(docstring) if doc_component and 'component' not in item_mark_names: - item.add_marker(pytest.mark.component(doc_component[0])) + item.add_marker(pytest.mark.component(doc_component[0].lower())) doc_importance = importance_regex.findall(docstring) if doc_importance and 'importance' not in item_mark_names: - item.add_marker(pytest.mark.importance(doc_importance[0])) + item.add_marker(pytest.mark.importance(doc_importance[0].lower())) doc_team = team_regex.findall(docstring) if doc_team and 'team' not in item_mark_names: item.add_marker(pytest.mark.team(doc_team[0].lower())) From 46b510fb74f818ff9bcd376684f89c70c25f2356 Mon Sep 17 00:00:00 2001 From: vsedmik <46570670+vsedmik@users.noreply.github.com> Date: Thu, 30 May 2024 13:42:23 +0200 Subject: [PATCH 04/31] Fix HTTP proxy teardown (#15229) --- pytest_fixtures/component/http_proxy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest_fixtures/component/http_proxy.py b/pytest_fixtures/component/http_proxy.py index fcf81c09eff..6fea394f4ad 100644 --- a/pytest_fixtures/component/http_proxy.py +++ b/pytest_fixtures/component/http_proxy.py @@ -29,4 +29,5 @@ def setup_http_proxy(request, module_manifest_org, target_sat): yield http_proxy, request.param target_sat.update_setting('content_default_http_proxy', content_proxy_value) target_sat.update_setting('http_proxy', general_proxy_value) - http_proxy.delete() + if http_proxy: + http_proxy.delete() From 1b5784bd8d1fdc71154de70b73506327387c211c Mon Sep 17 00:00:00 2001 From: vsedmik <46570670+vsedmik@users.noreply.github.com> Date: Thu, 30 May 2024 16:54:00 +0200 Subject: [PATCH 05/31] Add test case to verify artifacts repair at the Satellite side (#14976) * Add test case to verify artifacts repair at Capsule * Merge yum/file and docker/AC into one test case * Add test case to verify artifacts repair at Satellite * Let the test run through CLI to test hammer too * Update CODEOWNERS and lint fix --- .github/CODEOWNERS | 1 + robottelo/cli/contentview.py | 6 ++ robottelo/cli/product.py | 25 ++--- robottelo/cli/repository.py | 7 ++ tests/foreman/cli/test_artifacts.py | 159 ++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 tests/foreman/cli/test_artifacts.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e69f64c0973..535783dcf66 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -41,6 +41,7 @@ /tests/foreman/api/test_subscription.py @SatelliteQE/phoenix /tests/foreman/api/test_syncplan.py @SatelliteQE/phoenix /tests/foreman/cli/test_activationkey.py @SatelliteQE/phoenix +/tests/foreman/cli/test_artifacts.py @SatelliteQE/phoenix /tests/foreman/cli/test_capsulecontent.py @SatelliteQE/phoenix /tests/foreman/cli/test_contentaccess.py @SatelliteQE/phoenix /tests/foreman/cli/test_contentcredentials.py @SatelliteQE/phoenix diff --git a/robottelo/cli/contentview.py b/robottelo/cli/contentview.py index 5dfcd87721b..b780df66de5 100644 --- a/robottelo/cli/contentview.py +++ b/robottelo/cli/contentview.py @@ -172,6 +172,12 @@ def version_republish_repositories(cls, options): cls.command_sub = 'version republish-repositories' return cls.execute(cls._construct_command(options), ignore_stderr=True) + @classmethod + def version_verify_checksum(cls, options): + """Verify checksum of repository contents in the content view version.""" + cls.command_sub = 'version verify-checksum' + return cls.execute(cls._construct_command(options), ignore_stderr=True) + @classmethod def remove_from_environment(cls, options=None): """Remove content-view from an environment""" diff --git a/robottelo/cli/product.py b/robottelo/cli/product.py index 90e0a534549..7a4facc052a 100644 --- a/robottelo/cli/product.py +++ b/robottelo/cli/product.py @@ -19,6 +19,7 @@ synchronize Sync a repository update Update a product update-proxy Updates an HTTP Proxy for a product + verify-checksum Verify checksum for one or more products """ from robottelo.cli.base import Base @@ -34,22 +35,14 @@ class Product(Base): @classmethod def remove_sync_plan(cls, options=None): - """ - Delete assignment sync plan and product. - """ - + """Delete assignment sync plan and product.""" cls.command_sub = 'remove-sync-plan' - return cls.execute(cls._construct_command(options)) @classmethod def set_sync_plan(cls, options=None): - """ - Assign sync plan to product. - """ - + """Assign sync plan to product.""" cls.command_sub = 'set-sync-plan' - return cls.execute(cls._construct_command(options)) @classmethod @@ -60,10 +53,12 @@ def synchronize(cls, options=None): @classmethod def update_proxy(cls, options=None): - """ - Assign Http Proxy to products. - """ - + """Assign Http Proxy to products.""" cls.command_sub = 'update-proxy' - return cls.execute(cls._construct_command(options)) + + @classmethod + def verify_checksum(cls, options=None): + """Verify checksum for one or more products.""" + cls.command_sub = 'verify-checksum' + return cls.execute(cls._construct_command(options), ignore_stderr=True) diff --git a/robottelo/cli/repository.py b/robottelo/cli/repository.py index a17cc9fbcba..342851e68fc 100644 --- a/robottelo/cli/repository.py +++ b/robottelo/cli/repository.py @@ -18,6 +18,7 @@ synchronize Sync a repository update Update a repository upload-content Upload content into the repository + verify-checksum Verify checksum of repository contents """ from robottelo.cli.base import Base @@ -78,3 +79,9 @@ def upload_content(cls, options): """Upload content to repository.""" cls.command_sub = 'upload-content' return cls.execute(cls._construct_command(options), output_format='csv', ignore_stderr=True) + + @classmethod + def verify_checksum(cls, options): + """Verify checksum of repository contents.""" + cls.command_sub = 'verify-checksum' + return cls.execute(cls._construct_command(options), ignore_stderr=True) diff --git a/tests/foreman/cli/test_artifacts.py b/tests/foreman/cli/test_artifacts.py new file mode 100644 index 00000000000..13ddcff13be --- /dev/null +++ b/tests/foreman/cli/test_artifacts.py @@ -0,0 +1,159 @@ +"""Pulp artifacts related tests being run through CLI. + +:Requirement: Repositories + +:CaseAutomation: Automated + +:CaseComponent: Repositories + +:team: Phoenix-content + +:CaseImportance: High + +""" + +from datetime import datetime +import random + +from box import Box +import pytest + +from robottelo.config import settings +from robottelo.constants import ( + CONTAINER_REGISTRY_HUB, + CONTAINER_UPSTREAM_NAME, +) +from robottelo.constants.repos import ANSIBLE_GALAXY, CUSTOM_FILE_REPO +from robottelo.content_info import get_repo_files_urls_by_url + + +@pytest.fixture(scope='module') +def module_synced_content( + request, + module_target_sat, + module_org, + module_product, +): + """ + Create and sync one or more repositories and publish them in a CV. + + :param request: Repo to use - dict with options to create the repo. + :return: Box with created instances and Repository sync time. + """ + repo = module_target_sat.api.Repository(product=module_product, **request.param).create() + sync_time = datetime.utcnow().replace(microsecond=0) + repo.sync() + + cv = module_target_sat.api.ContentView(organization=module_org, repository=[repo]).create() + cv.publish() + + return Box(prod=module_product, repo=repo, cv=cv.read(), sync_time=sync_time) + + +@pytest.mark.stream +@pytest.mark.parametrize('repair_type', ['repo', 'cv', 'product']) +@pytest.mark.parametrize( + 'module_synced_content', + [ + {'content_type': 'yum', 'url': settings.repos.yum_0.url}, + {'content_type': 'file', 'url': CUSTOM_FILE_REPO}, + { + 'content_type': 'docker', + 'docker_upstream_name': CONTAINER_UPSTREAM_NAME, + 'url': CONTAINER_REGISTRY_HUB, + }, + { + 'content_type': 'ansible_collection', + 'url': ANSIBLE_GALAXY, + 'ansible_collection_requirements': '{collections: [ \ + { name: theforeman.foreman, version: "2.1.0" }, \ + { name: theforeman.operations, version: "0.1.0"} ]}', + }, + ], + indirect=True, + ids=['yum', 'file', 'docker', 'AC'], +) +@pytest.mark.parametrize('damage_type', ['destroy', 'corrupt']) +def test_positive_artifact_repair( + module_target_sat, + module_org, + module_lce_library, + module_synced_content, + damage_type, + repair_type, +): + """Test the verify-checksum task repairs artifacts of each supported content type correctly + at the Satellite side for repo, CVV and product when the artifacts were removed or corrupted + before. + + :id: 55c31fdc-bfa1-4af4-9adf-35c996eca974 + + :parametrized: yes + + :setup: + 1. Have a blank Satellite to avoid any artifacts already synced by other tests. + 2. Per parameter, create repository of each content type, publish it in a CV. + + :steps: + 1. Based on the repository content type + - find and pick one artifact for particular published file, or + - pick one artifact synced recently by the `module_synced_content` fixture. + 2. Cause desired type of damage to the artifact and verify the effect. + 3. Trigger desired variant of the repair (verify_checksum) task. + 4. Check if the artifact is back in shape. + + :expectedresults: + 1. Artifact is stored correctly based on the checksum. (yum and file) + 2. All variants of verify_checksum task are able to repair all types of damage for all + supported content types. + + """ + # Based on the repository content type + if module_synced_content.repo.content_type in ['yum', 'file']: + # Find and pick one artifact for particular published file. + sat_repo_url = module_target_sat.get_published_repo_url( + org=module_org.label, + lce=None if repair_type == 'repo' else module_lce_library.label, + cv=None if repair_type == 'repo' else module_synced_content.cv.label, + prod=module_synced_content.prod.label, + repo=module_synced_content.repo.label, + ) + sat_files_urls = get_repo_files_urls_by_url( + sat_repo_url, + extension='rpm' if module_synced_content.repo.content_type == 'yum' else 'iso', + ) + url = random.choice(sat_files_urls) + sum = module_target_sat.checksum_by_url(url, sum_type='sha256sum') + ai = module_target_sat.get_artifact_info(checksum=sum) + else: + # Pick one artifact synced recently by the `module_synced_content` fixture. + artifacts = module_target_sat.get_artifacts(since=module_synced_content.sync_time) + assert len(artifacts) > 0, 'No NEW artifacts found' + ai = module_target_sat.get_artifact_info(path=random.choice(artifacts)) + + # Cause desired type of damage to the artifact and verify the effect. + if damage_type == 'destroy': + module_target_sat.execute(f'rm -f {ai.path}') + with pytest.raises(FileNotFoundError): + module_target_sat.get_artifact_info(path=ai.path) + elif damage_type == 'corrupt': + res = module_target_sat.execute(f'truncate -s {random.randrange(1, ai.size)} {ai.path}') + assert res.status == 0, f'Artifact truncation failed: {res.stderr}' + assert module_target_sat.get_artifact_info(path=ai.path) != ai, 'Artifact corruption failed' + else: + raise ValueError(f'Unsupported damage type: {damage_type}') + + # Trigger desired variant of repair (verify_checksum) task. + if repair_type == 'repo': + module_target_sat.cli.Repository.verify_checksum({'id': module_synced_content.repo.id}) + elif repair_type == 'cv': + cvv_id = module_synced_content.cv.version[0].id + module_target_sat.cli.ContentView.version_verify_checksum({'id': cvv_id}) + elif repair_type == 'product': + module_target_sat.cli.Product.verify_checksum({'ids': module_synced_content.prod.id}) + else: + raise ValueError(f'Unsupported repair type: {repair_type}') + + # Check if the artifact is back in shape. + fixed_ai = module_target_sat.get_artifact_info(path=ai.path) + assert fixed_ai == ai, f'Artifact restoration failed: {fixed_ai} != {ai}' From 47b9906ddce87896ce81ae53efd7200837e60bb9 Mon Sep 17 00:00:00 2001 From: vsedmik <46570670+vsedmik@users.noreply.github.com> Date: Thu, 30 May 2024 18:47:22 +0200 Subject: [PATCH 06/31] Updates for CV component eval (#15043) * Few updates for CV component eval * Address comments --- tests/foreman/cli/test_contentview.py | 241 +++----------------- tests/foreman/cli/test_contentviewfilter.py | 2 - tests/foreman/ui/test_contentview_old.py | 43 +--- 3 files changed, 42 insertions(+), 244 deletions(-) diff --git a/tests/foreman/cli/test_contentview.py b/tests/foreman/cli/test_contentview.py index d85db9d6652..7f2a8d1c75c 100644 --- a/tests/foreman/cli/test_contentview.py +++ b/tests/foreman/cli/test_contentview.py @@ -1010,16 +1010,19 @@ def test_positive_add_module_stream_filter_rule(self, module_org, target_sat): assert filter_info['rules'][0]['id'] == content_view_filter_rule['rule-id'] @pytest.mark.tier1 - def test_positive_add_custom_repo_by_name(self, module_org, module_product, module_target_sat): - """Associate custom content to a content view with name + @pytest.mark.parametrize('add_by_name', [True, False], ids=['name', 'id']) + def test_positive_add_custom_repo_by( + self, module_org, module_product, module_target_sat, add_by_name + ): + """Associate custom content to a content view with repository name and/or id. :id: 62431e11-bec6-4444-abb0-e3758ba25fd8 - :expectedresults: whether repos are added to cv. + :parametrized: yes - :CaseImportance: Critical + :expectedresults: Repositories can be added to the CV using their name and/or id. - :BZ: 1343006 + :CaseImportance: Critical """ new_repo = module_target_sat.cli_factory.make_repository( {'content-type': 'yum', 'product-id': module_product.id} @@ -1028,17 +1031,19 @@ def test_positive_add_custom_repo_by_name(self, module_org, module_product, modu module_target_sat.cli.Repository.synchronize({'id': new_repo['id']}) # Create CV new_cv = module_target_sat.cli_factory.make_content_view({'organization-id': module_org.id}) - # Associate repo to CV with names. - module_target_sat.cli.ContentView.add_repository( - { - 'name': new_cv['name'], - 'organization': module_org.name, - 'product': module_product.name, - 'repository': new_repo['name'], - } + # Associate repo to CV + opts = { + 'name': new_cv['name'], + 'organization': module_org.name, + 'product': module_product.name, + } + opts.update( + {'repository': new_repo['name']} if add_by_name else {'repository-id': new_repo['id']} ) + module_target_sat.cli.ContentView.add_repository(opts) new_cv = module_target_sat.cli.ContentView.info({'id': new_cv['id']}) assert new_cv['yum-repositories'][0]['name'] == new_repo['name'] + assert new_cv['yum-repositories'][0]['id'] == new_repo['id'] @pytest.mark.tier2 def test_negative_add_same_yum_repo_twice(self, module_org, module_product, module_target_sat): @@ -2326,38 +2331,6 @@ def test_positive_clone_with_diff_env(self, module_org, module_target_sat): 'lifecycle-environments' ] - @pytest.mark.stubbed - def test_positive_restart_dynflow_promote(self): - """attempt to restart a failed content view promotion - - :id: 2be23f85-3f62-4319-87ea-41f28cf401dc - - :steps: - 1. (Somehow) cause a CV promotion to fail. Not exactly sure how - yet. - 2. Via Dynflow, restart promotion - - :expectedresults: Promotion is restarted. - - :CaseAutomation: NotAutomated - """ - - @pytest.mark.stubbed - def test_positive_restart_dynflow_publish(self): - """attempt to restart a failed content view publish - - :id: 24468014-46b2-403e-8ed6-2daeda5a0163 - - :steps: - 1. (Somehow) cause a CV publish to fail. Not exactly sure how - yet. - 2. Via Dynflow, restart publish - - :expectedresults: Publish is restarted. - - :CaseAutomation: NotAutomated - """ - @pytest.mark.tier2 @pytest.mark.skipif( (not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url' @@ -2525,132 +2498,6 @@ def test_positive_remove_promoted_cv_version_from_default_env( } assert {lce_dev['name']} == content_view_version_lce_names - @pytest.mark.tier2 - @pytest.mark.skipif( - (not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url' - ) - def test_positive_remove_cv_version_from_env(self, module_org, module_target_sat): - """Remove promoted content view version from environment - - :id: 577757ac-b184-4ece-9310-182dd5ceb718 - - :steps: - - 1. Create a content view - 2. Add a yum repo and a docker repo to the content view - 3. Publish the content view - 4. Promote the content view version to multiple environments - Library -> DEV -> QE -> STAGE -> PROD - 5. remove the content view version from PROD environment - 6. Assert: content view version exists only in Library, DEV, QE, - STAGE and not in PROD - 7. Promote again from STAGE -> PROD - - :expectedresults: Content view version exist in Library, DEV, QE, - STAGE, PROD - - :CaseImportance: High - """ - lce_dev = module_target_sat.cli_factory.make_lifecycle_environment( - {'organization-id': module_org.id} - ) - lce_qe = module_target_sat.cli_factory.make_lifecycle_environment( - {'organization-id': module_org.id, 'prior': lce_dev['name']} - ) - lce_stage = module_target_sat.cli_factory.make_lifecycle_environment( - {'organization-id': module_org.id, 'prior': lce_qe['name']} - ) - lce_prod = module_target_sat.cli_factory.make_lifecycle_environment( - {'organization-id': module_org.id, 'prior': lce_stage['name']} - ) - custom_yum_product = module_target_sat.cli_factory.make_product( - {'organization-id': module_org.id} - ) - custom_yum_repo = module_target_sat.cli_factory.make_repository( - { - 'content-type': 'yum', - 'product-id': custom_yum_product['id'], - 'url': settings.repos.yum_1.url, - } - ) - module_target_sat.cli.Repository.synchronize({'id': custom_yum_repo['id']}) - docker_product = module_target_sat.cli_factory.make_product( - {'organization-id': module_org.id} - ) - docker_repository = module_target_sat.cli_factory.make_repository( - { - 'content-type': 'docker', - 'docker-upstream-name': constants.CONTAINER_UPSTREAM_NAME, - 'name': gen_string('alpha', 20), - 'product-id': docker_product['id'], - 'url': constants.CONTAINER_REGISTRY_HUB, - } - ) - module_target_sat.cli.Repository.synchronize({'id': docker_repository['id']}) - content_view = module_target_sat.cli_factory.make_content_view( - {'organization-id': module_org.id} - ) - for repo in [custom_yum_repo, docker_repository]: - module_target_sat.cli.ContentView.add_repository( - { - 'id': content_view['id'], - 'organization-id': module_org.id, - 'repository-id': repo['id'], - } - ) - module_target_sat.cli.ContentView.publish({'id': content_view['id']}) - content_view_versions = module_target_sat.cli.ContentView.info({'id': content_view['id']})[ - 'versions' - ] - assert len(content_view_versions) > 0 - content_view_version = content_view_versions[-1] - for lce in [lce_dev, lce_qe, lce_stage, lce_prod]: - module_target_sat.cli.ContentView.version_promote( - {'id': content_view_version['id'], 'to-lifecycle-environment-id': lce['id']} - ) - # ensure that the published content version is in Library, DEV, QE, - # STAGE and PROD environments - assert { - constants.ENVIRONMENT, - lce_dev['name'], - lce_qe['name'], - lce_stage['name'], - lce_prod['name'], - } == _get_content_view_version_lce_names_set( - content_view['id'], content_view_version['id'], sat=module_target_sat - ) - # remove content view version from PROD lifecycle environment - module_target_sat.cli.ContentView.remove_from_environment( - { - 'id': content_view['id'], - 'organization-id': module_org.id, - 'lifecycle-environment': lce_prod['name'], - } - ) - # ensure content view version is not in PROD and only in Library, DEV, - # QE and STAGE environments - assert { - constants.ENVIRONMENT, - lce_dev['name'], - lce_qe['name'], - lce_stage['name'], - } == _get_content_view_version_lce_names_set( - content_view['id'], content_view_version['id'], sat=module_target_sat - ) - # promote content view version to PROD environment again - module_target_sat.cli.ContentView.version_promote( - {'id': content_view_version['id'], 'to-lifecycle-environment-id': lce_prod['id']} - ) - assert { - constants.ENVIRONMENT, - lce_dev['name'], - lce_qe['name'], - lce_stage['name'], - lce_prod['name'], - } == _get_content_view_version_lce_names_set( - content_view['id'], content_view_version['id'], sat=module_target_sat - ) - @pytest.mark.tier3 @pytest.mark.skipif( (not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url' @@ -2661,15 +2508,17 @@ def test_positive_remove_cv_version_from_multi_env(self, module_org, module_targ :id: 997cfd7d-9029-47e2-a41e-84f4370b5ce5 :steps: - 1. Create a content view 2. Add a yum repo and a docker to the content view 3. Publish the content view 4. Promote the content view version to multiple environments Library -> DEV -> QE -> STAGE -> PROD 5. Remove content view version from QE, STAGE and PROD + 6. Assert the CVV exists only in Library and DEV + 7. Promote again from DEV -> QE -> STAGE + 8. Assert the CVV exists in Library, DEV, QE and STAGE - :expectedresults: Content view version exists only in Library, DEV + :expectedresults: Content view version exists in the right LCEs for each step. :CaseImportance: High """ @@ -2741,8 +2590,7 @@ def test_positive_remove_cv_version_from_multi_env(self, module_org, module_targ } == _get_content_view_version_lce_names_set( content_view['id'], content_view_version['id'], sat=module_target_sat ) - # remove content view version from QE, STAGE, PROD lifecycle - # environments + # remove content view version from QE, STAGE, PROD lifecycle environments for lce in [lce_qe, lce_stage, lce_prod]: module_target_sat.cli.ContentView.remove_from_environment( { @@ -2751,11 +2599,24 @@ def test_positive_remove_cv_version_from_multi_env(self, module_org, module_targ 'lifecycle-environment': lce['name'], } ) - # ensure content view version is not in PROD and only in Library, DEV, - # QE and STAGE environments + # ensure content view version is not in QE, STAGE, PROD and only in Library and DEV envs assert {constants.ENVIRONMENT, lce_dev['name']} == _get_content_view_version_lce_names_set( content_view['id'], content_view_version['id'], sat=module_target_sat ) + # Promote again from DEV -> QE -> STAGE + for lce in [lce_qe, lce_stage]: + module_target_sat.cli.ContentView.version_promote( + {'id': content_view_version['id'], 'to-lifecycle-environment-id': lce['id']} + ) + # ensure content view version is not in PROD and only in Library, DEV, QE and STAGE envs + assert { + constants.ENVIRONMENT, + lce_dev['name'], + lce_qe['name'], + lce_stage['name'], + } == _get_content_view_version_lce_names_set( + content_view['id'], content_view_version['id'], sat=module_target_sat + ) @pytest.mark.stubbed @pytest.mark.tier3 @@ -3525,32 +3386,6 @@ def test_positive_arbitrary_file_repo_removal( ) assert cv['file-repositories'][0]['id'] != repo['id'] - @pytest.mark.stubbed - @pytest.mark.tier3 - @pytest.mark.upgrade - def test_positive_arbitrary_file_sync_over_capsule(self): - """Check a File Repository with Arbitrary File can be added to a - Content View is synced throughout capsules - - :id: ffa59550-464c-40dc-9bd2-444331f73708 - - :Setup: - 1. Create a File Repository (FR) - 2. Upload an arbitrary file to it - 3. Create a Content View (CV) - 4. Add the FR to the CV - 5. Create a Capsule - 6. Connect the Capsule with Satellite/Foreman host - - :steps: - 1. Start synchronization - - :expectedresults: Check CV with FR is synced over Capsule - - :CaseAutomation: NotAutomated - - """ - @pytest.mark.tier3 def test_positive_arbitrary_file_repo_promotion( self, module_org, module_product, module_target_sat diff --git a/tests/foreman/cli/test_contentviewfilter.py b/tests/foreman/cli/test_contentviewfilter.py index df9912be342..3490b16f530 100644 --- a/tests/foreman/cli/test_contentviewfilter.py +++ b/tests/foreman/cli/test_contentviewfilter.py @@ -768,8 +768,6 @@ def test_negative_update_with_name(self, new_name, content_view, module_target_s :expectedresults: Content view filter is not updated - :BZ: 1328943 - :CaseImportance: Critical """ cvf_name = gen_string('utf8') diff --git a/tests/foreman/ui/test_contentview_old.py b/tests/foreman/ui/test_contentview_old.py index ce20ed0aecd..9d8bba4c6c1 100644 --- a/tests/foreman/ui/test_contentview_old.py +++ b/tests/foreman/ui/test_contentview_old.py @@ -626,53 +626,18 @@ def test_positive_promote_multiple_with_docker_repo( assert lce.name in result['Environments'] -@pytest.mark.tier2 -def test_positive_promote_with_docker_repo_composite( - session, module_target_sat, module_org, module_prod -): - """Add docker repository to composite content view and publish it. - Then promote it to the next available lifecycle-environment. - - :id: 1c7817c7-60b5-4383-bc6f-2878c2b27fa5 - - :expectedresults: Docker repository is promoted to content view - found in the specific lifecycle-environment. - - :CaseImportance: High - """ - lce = module_target_sat.api.LifecycleEnvironment(organization=module_org).create() - repo = module_target_sat.api.Repository( - url=CONTAINER_REGISTRY_HUB, product=module_prod, content_type=REPO_TYPE['docker'] - ).create() - content_view = module_target_sat.api.ContentView( - composite=False, organization=module_org, repository=[repo] - ).create() - content_view.publish() - content_view = content_view.read() - composite_cv = module_target_sat.api.ContentView( - component=[content_view.version[-1]], composite=True, organization=module_org - ).create() - composite_cv.publish() - with session: - result = session.contentview.promote(composite_cv.name, VERSION, lce.name) - assert f'Promoted to {lce.name}' in result['Status'] - assert lce.name in result['Environments'] - - @pytest.mark.tier2 @pytest.mark.upgrade def test_positive_promote_multiple_with_docker_repo_composite( session, module_target_sat, module_org, module_prod ): - """Add docker repository to composite content view and publish it - Then promote it to the multiple available lifecycle environments. + """Add docker repository to composite content view and publish it. + Then promote it to multiple available lifecycle environments. :id: b735b1fa-3d60-4fc0-92d2-4af0ab003097 - :expectedresults: Docker repository is promoted to content view - found in the specific lifecycle-environments. - - :CaseImportance: Low + :expectedresults: Docker repository published in a content view + is promoted to multiple lifecycle-environments. """ repo = module_target_sat.api.Repository( url=CONTAINER_REGISTRY_HUB, product=module_prod, content_type=REPO_TYPE['docker'] From e48b00cb466572ceb0c3660fba8c5ed91578ad17 Mon Sep 17 00:00:00 2001 From: Gaurav Talreja Date: Fri, 31 May 2024 01:04:04 +0530 Subject: [PATCH 07/31] Remove nailgun entity_mixin import from discovery tests (#15164) Remove nailgun entity_mixin import from discovery test Signed-off-by: Gaurav Talreja --- robottelo/config/__init__.py | 9 +++++++++ tests/foreman/destructive/test_discoveredhost.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/robottelo/config/__init__.py b/robottelo/config/__init__.py index cca2258c7e6..d761dd06e91 100644 --- a/robottelo/config/__init__.py +++ b/robottelo/config/__init__.py @@ -90,6 +90,15 @@ def get_url(): return urlunsplit((scheme, hostname, '', '', '')) +def admin_nailgun_config(): + """Return a NailGun configuration file constructed from default admin user credentials. + + :return: ``nailgun.config.ServerConfig`` object, populated from admin user credentials. + + """ + return ServerConfig(get_url(), get_credentials(), verify=settings.server.verify_ca) + + def user_nailgun_config(username=None, password=None): """Return a NailGun configuration file constructed from default values. diff --git a/tests/foreman/destructive/test_discoveredhost.py b/tests/foreman/destructive/test_discoveredhost.py index 86568230e0d..e779aab52c9 100644 --- a/tests/foreman/destructive/test_discoveredhost.py +++ b/tests/foreman/destructive/test_discoveredhost.py @@ -13,10 +13,10 @@ from copy import copy import re -from nailgun import entity_mixins import pytest from wait_for import TimedOutError, wait_for +from robottelo.config import admin_nailgun_config from robottelo.logging import logger pytestmark = pytest.mark.destructive @@ -124,7 +124,7 @@ def _assert_discovered_host(host, channel=None, user_config=None, sat=None): # raise assertion error raise AssertionError('Timed out waiting for "/facts" 201 response') from err - default_config = entity_mixins.DEFAULT_SERVER_CONFIG + default_config = admin_nailgun_config() try: wait_for( From c4dbf8182ce42f4a969f651cb487e12a87082b8b Mon Sep 17 00:00:00 2001 From: Griffin Sullivan <48397354+Griffin-Sullivan@users.noreply.github.com> Date: Thu, 30 May 2024 17:56:51 -0400 Subject: [PATCH 08/31] Add PIT marker to clone tests (#15243) --- tests/foreman/destructive/test_clone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/foreman/destructive/test_clone.py b/tests/foreman/destructive/test_clone.py index e435f3fbfcf..b590eff7d7a 100644 --- a/tests/foreman/destructive/test_clone.py +++ b/tests/foreman/destructive/test_clone.py @@ -22,6 +22,7 @@ pytestmark = pytest.mark.destructive +@pytest.mark.pit_server @pytest.mark.e2e @pytest.mark.parametrize( "sat_ready_rhel", @@ -130,7 +131,6 @@ def test_positive_clone_backup( ) -@pytest.mark.pit_server def test_positive_list_tasks(target_sat): """Test that satellite-clone --list-tasks command doesn't fail. From ba922c8078afb9370db578b664ff925f398938df Mon Sep 17 00:00:00 2001 From: Gaurav Talreja Date: Fri, 31 May 2024 06:36:11 +0530 Subject: [PATCH 09/31] Modify test_positive_import_all_roles to cover non-admin scenario (#15171) Signed-off-by: Gaurav Talreja --- tests/foreman/destructive/test_ansible.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/foreman/destructive/test_ansible.py b/tests/foreman/destructive/test_ansible.py index f48b7e996b0..9c4966f153e 100644 --- a/tests/foreman/destructive/test_ansible.py +++ b/tests/foreman/destructive/test_ansible.py @@ -15,6 +15,8 @@ from fauxfactory import gen_string import pytest +from robottelo.config import settings + pytestmark = [pytest.mark.destructive, pytest.mark.upgrade] @@ -45,7 +47,8 @@ def test_positive_persistent_ansible_cfg_change(target_sat): assert param in target_sat.execute(f'cat {ansible_cfg}').stdout.splitlines() -def test_positive_import_all_roles(target_sat): +@pytest.mark.parametrize('auth_type', ['admin', 'non-admin']) +def test_positive_import_all_roles(request, target_sat, function_org, auth_type): """Import all Ansible roles available by default. :id: 53fe3857-a08f-493d-93c7-3fed331ed391 @@ -63,7 +66,23 @@ def test_positive_import_all_roles(target_sat): :expectedresults: All roles are imported successfully. One role is deleted successfully. """ - with target_sat.ui_session() as session: + username = settings.server.admin_username + password = settings.server.admin_password + if auth_type == 'non-admin': + ansible_manager_role = target_sat.api.Role().search( + query={'search': 'name="Ansible Roles Manager"'} + ) + user = target_sat.api.User( + role=ansible_manager_role, + admin=True, + login=gen_string('alphanumeric'), + password=password, + organization=[function_org], + ).create() + request.addfinalizer(user.delete) + username = user.login + with target_sat.ui_session(user=username, password=password) as session: + session.organization.select(function_org.name) assert session.ansibleroles.import_all_roles() == session.ansibleroles.imported_roles_count assert int(session.ansiblevariables.read_total_variables()) > 0 # The choice of role to be deleted is arbitrary; any of the roles present on Satellite From 8cef935fe2327da7e15ce4baa0852b8ba2332e5e Mon Sep 17 00:00:00 2001 From: Tasos Papaioannou Date: Fri, 31 May 2024 09:44:58 -0400 Subject: [PATCH 10/31] Remove nailgun.entities imports in tests/foreman/longrun (#15220) * Remove nailgun.entities imports in tests/foreman/longrun * Remove one-line helper function in longrun test --- tests/foreman/longrun/test_inc_updates.py | 35 +++++++++++------------ tests/foreman/longrun/test_oscap.py | 15 ++++++---- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/foreman/longrun/test_inc_updates.py b/tests/foreman/longrun/test_inc_updates.py index 3f00f09662e..b4a0d2cfccd 100644 --- a/tests/foreman/longrun/test_inc_updates.py +++ b/tests/foreman/longrun/test_inc_updates.py @@ -14,7 +14,6 @@ from datetime import datetime, timedelta -from nailgun import entities import pytest from robottelo.config import settings @@ -33,10 +32,10 @@ @pytest.fixture(scope='module') -def module_lce_library(module_sca_manifest_org): +def module_lce_library(module_target_sat, module_sca_manifest_org): """Returns the Library lifecycle environment from chosen organization""" return ( - entities.LifecycleEnvironment() + module_target_sat.api.LifecycleEnvironment() .search( query={ 'search': f'name={ENVIRONMENT} and ' f'organization_id={module_sca_manifest_org.id}' @@ -47,13 +46,15 @@ def module_lce_library(module_sca_manifest_org): @pytest.fixture(scope='module') -def dev_lce(module_sca_manifest_org): - return entities.LifecycleEnvironment(name='DEV', organization=module_sca_manifest_org).create() +def dev_lce(module_target_sat, module_sca_manifest_org): + return module_target_sat.api.LifecycleEnvironment( + name='DEV', organization=module_sca_manifest_org + ).create() @pytest.fixture(scope='module') -def qe_lce(module_sca_manifest_org, dev_lce): - return entities.LifecycleEnvironment( +def qe_lce(module_target_sat, module_sca_manifest_org, dev_lce): + return module_target_sat.api.LifecycleEnvironment( name='QE', prior=dev_lce, organization=module_sca_manifest_org ).create() @@ -86,9 +87,9 @@ def custom_repo(module_sca_manifest_org, module_target_sat): @pytest.fixture(scope='module') -def module_cv(module_sca_manifest_org, sat_client_repo, custom_repo): +def module_cv(module_target_sat, module_sca_manifest_org, sat_client_repo, custom_repo): """Publish both repos into module CV""" - module_cv = entities.ContentView( + module_cv = module_target_sat.api.ContentView( organization=module_sca_manifest_org, repository=[sat_client_repo.id, custom_repo.id], ).create() @@ -101,7 +102,7 @@ def module_ak( module_sca_manifest_org, module_cv, custom_repo, module_lce_library, module_target_sat ): """Create a module AK in Library LCE""" - ak = entities.ActivationKey( + ak = module_target_sat.api.ActivationKey( content_view=module_cv, environment=module_lce_library, organization=module_sca_manifest_org, @@ -158,7 +159,7 @@ def host( ) # Add filter of type include but do not include anything. # this will hide all RPMs from selected erratum before publishing. - entities.RPMContentViewFilter( + module_target_sat.api.RPMContentViewFilter( content_view=module_cv, inclusion=True, name='Include Nothing' ).create() module_cv.publish() @@ -166,11 +167,6 @@ def host( return rhel7_contenthost_module -def get_applicable_errata(repo): - """Retrieves applicable errata for the given repo""" - return entities.Errata(repository=repo).search(query={'errata_restrict_applicable': True}) - - @pytest.mark.tier4 @pytest.mark.upgrade def test_positive_noapply_api( @@ -194,8 +190,11 @@ def test_positive_noapply_api( cvv = versions[-1].read() cvv.promote(data={'environment_ids': dev_lce.id}) - # Get the applicable errata - errata_list = get_applicable_errata(custom_repo) + # Get the applicable errata for the given repo + errata_list = module_target_sat.api.Errata(repository=custom_repo).search( + query={'errata_restrict_applicable': True} + ) + assert len(errata_list) > 0 # Apply incremental update using the first applicable errata diff --git a/tests/foreman/longrun/test_oscap.py b/tests/foreman/longrun/test_oscap.py index 28908482e82..d448f8d541d 100644 --- a/tests/foreman/longrun/test_oscap.py +++ b/tests/foreman/longrun/test_oscap.py @@ -14,7 +14,6 @@ from broker import Broker from fauxfactory import gen_string -from nailgun import entities import pytest from robottelo.config import settings @@ -69,15 +68,19 @@ def default_proxy(module_target_sat): @pytest.fixture(scope='module') -def lifecycle_env(module_org): +def lifecycle_env(module_target_sat, module_org): """Create lifecycle environment""" - return entities.LifecycleEnvironment(organization=module_org, name=gen_string('alpha')).create() + return module_target_sat.api.LifecycleEnvironment( + organization=module_org, name=gen_string('alpha') + ).create() @pytest.fixture(scope='module') -def content_view(module_org): +def content_view(module_target_sat, module_org): """Create content view""" - return entities.ContentView(organization=module_org, name=gen_string('alpha')).create() + return module_target_sat.api.ContentView( + organization=module_org, name=gen_string('alpha') + ).create() @pytest.fixture(scope='module', autouse=True) @@ -90,7 +93,7 @@ def activation_key(module_target_sat, module_org, lifecycle_env, content_view): ] for repo in repo_values: - activation_key = entities.ActivationKey( + activation_key = module_target_sat.api.ActivationKey( name=repo.get('akname'), environment=lifecycle_env, organization=module_org ).create() # Setup org for a custom repo for RHEL6, RHEL7 and RHEL8. From 9f38177e1ee0116abb401a9a1ac3aa7614210904 Mon Sep 17 00:00:00 2001 From: Gaurav Talreja Date: Fri, 31 May 2024 20:41:44 +0530 Subject: [PATCH 11/31] Modify test_positive_ansible_custom_role to cover non-admin user scenario (#15163) Modify test_positive_ansible_custom_role to cover non-admin scenario Signed-off-by: Gaurav Talreja --- tests/foreman/ui/test_ansible.py | 63 +++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/tests/foreman/ui/test_ansible.py b/tests/foreman/ui/test_ansible.py index e5d35d5962d..0b29d2c2c17 100644 --- a/tests/foreman/ui/test_ansible.py +++ b/tests/foreman/ui/test_ansible.py @@ -15,7 +15,12 @@ import yaml from robottelo import constants -from robottelo.config import robottelo_tmp_dir, settings +from robottelo.config import ( + admin_nailgun_config, + robottelo_tmp_dir, + settings, + user_nailgun_config, +) class TestAnsibleCfgMgmt: @@ -522,8 +527,15 @@ def test_positive_config_report_ansible( @pytest.mark.no_containers @pytest.mark.rhel_ver_match('9') + @pytest.mark.parametrize('auth_type', ['admin', 'non-admin']) def test_positive_ansible_custom_role( - self, target_sat, module_org, module_ak_with_cv, rhel_contenthost, request + self, + auth_type, + target_sat, + module_org, + module_ak_with_cv, + rhel_contenthost, + request, ): """ Test Config report generation with Custom Ansible Role @@ -545,14 +557,29 @@ def test_positive_ansible_custom_role( :customerscenario: true """ + user_cfg = admin_nailgun_config() + password = settings.server.admin_password + if auth_type == 'non-admin': + ansible_manager_role = target_sat.api.Role().search( + query={'search': 'name="Ansible Roles Manager"'} + ) + username = gen_string('alphanumeric') + target_sat.api.User( + role=ansible_manager_role, + admin=True, + login=username, + password=password, + organization=[module_org], + ).create() + user_cfg = user_nailgun_config(username, password) @request.addfinalizer def _finalize(): result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] - target_sat.execute('rm -rvf /etc/ansible/roles/custom_role') + target_sat.execute(f'rm -rvf /etc/ansible/roles/{SELECTED_ROLE}') - SELECTED_ROLE = 'custom_role' + SELECTED_ROLE = gen_string('alphanumeric') playbook = f'{robottelo_tmp_dir}/playbook.yml' data = { 'name': 'Copy ssh keys', @@ -565,30 +592,37 @@ def _finalize(): }, 'with_items': ['id_rsa_foreman_proxy.pub', 'id_rsa_foreman_proxy'], } + with open(playbook, 'w') as f: yaml.dump(data, f, sort_keys=False, default_flow_style=False) - target_sat.execute('mkdir /etc/ansible/roles/custom_role') - target_sat.put(playbook, '/etc/ansible/roles/custom_role/playbook.yaml') + target_sat.execute(f'mkdir /etc/ansible/roles/{SELECTED_ROLE}') + target_sat.put(playbook, f'/etc/ansible/roles/{SELECTED_ROLE}/playbook.yaml') result = rhel_contenthost.register(module_org, None, module_ak_with_cv.name, target_sat) assert result.status == 0, f'Failed to register host: {result.stderr}' proxy_id = target_sat.nailgun_smart_proxy.id target_host = rhel_contenthost.nailgun_host - target_sat.api.AnsibleRoles().sync( + target_sat.api.AnsibleRoles(server_config=user_cfg).sync( data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} ) - target_sat.cli.Host.ansible_roles_assign( - {'id': target_host.id, 'ansible-roles': SELECTED_ROLE} + ROLE_ID = [ + target_sat.api.AnsibleRoles().search(query={'search': f'name={SELECTED_ROLE}'})[0].id + ] + # Assign first 2 roles to HG and verify it + target_sat.api.Host(server_config=user_cfg, id=target_host.id).assign_ansible_roles( + data={'ansible_role_ids': ROLE_ID} ) - host_roles = target_host.list_ansible_roles() + host_roles = target_sat.api.Host( + server_config=user_cfg, id=target_host.id + ).list_ansible_roles() assert host_roles[0]['name'] == SELECTED_ROLE template_id = ( - target_sat.api.JobTemplate() + target_sat.api.JobTemplate(server_config=user_cfg) .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] .id ) - job = target_sat.api.JobInvocation().run( + job = target_sat.api.JobInvocation(server_config=user_cfg).run( synchronous=False, data={ 'job_template_id': template_id, @@ -599,9 +633,10 @@ def _finalize(): target_sat.wait_for_tasks( f'resource_type = JobInvocation and resource_id = {job["id"]}', poll_timeout=1000 ) - result = target_sat.api.JobInvocation(id=job['id']).read() + result = target_sat.api.JobInvocation(server_config=user_cfg, id=job['id']).read() assert result.succeeded == 1 - with target_sat.ui_session() as session: + + with target_sat.ui_session(user=user_cfg.auth[0], password=password) as session: session.organization.select(module_org.name) session.location.select(constants.DEFAULT_LOC) assert session.host.search(target_host.name)[0]['Name'] == rhel_contenthost.hostname From e65bda0b00a5bd9d092b1469671ec0abe1fc77a7 Mon Sep 17 00:00:00 2001 From: Peter Ondrejka Date: Mon, 3 Jun 2024 08:43:29 +0200 Subject: [PATCH 12/31] eol version check (#13253) --- conf/subscription.yaml.template | 2 + robottelo/config/validators.py | 1 + robottelo/constants/__init__.py | 1 + tests/foreman/api/test_eol_banner.py | 55 ++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 tests/foreman/api/test_eol_banner.py diff --git a/conf/subscription.yaml.template b/conf/subscription.yaml.template index 54bb596a401..4de58229816 100644 --- a/conf/subscription.yaml.template +++ b/conf/subscription.yaml.template @@ -5,3 +5,5 @@ SUBSCRIPTION: RHN_PASSWORD: # subscription pool id RHN_POOLID: + # lifecycle API url + LIFECYCLE_API_URL: diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index a47dba2e211..971ac082d13 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -43,6 +43,7 @@ Validator('subscription.rhn_username', must_exist=True), Validator('subscription.rhn_password', must_exist=True), Validator('subscription.rhn_poolid', must_exist=True), + Validator('subscription.lifecycle_api_url', must_exist=True), ], ansible_hub=[ Validator('ansible_hub.url', must_exist=True), diff --git a/robottelo/constants/__init__.py b/robottelo/constants/__init__.py index eda54a5c3f0..b91b2d67ba0 100644 --- a/robottelo/constants/__init__.py +++ b/robottelo/constants/__init__.py @@ -2068,6 +2068,7 @@ "DELETE", "PATCH", ] +LIFECYCLE_METADATA_FILE = '/usr/share/satellite/lifecycle-metadata.yml' OPENSSH_RECOMMENDATION = 'Decreased security: OpenSSH config permissions' DNF_RECOMMENDATION = ( diff --git a/tests/foreman/api/test_eol_banner.py b/tests/foreman/api/test_eol_banner.py new file mode 100644 index 00000000000..99957793336 --- /dev/null +++ b/tests/foreman/api/test_eol_banner.py @@ -0,0 +1,55 @@ +"""Test module for EOL tracking + +:Requirement: Dashboard + +:CaseAutomation: Automated + +:CaseComponent: Dashboard + +:Team: Endeavour + +:CaseImportance: High +""" + +from datetime import datetime + +import pytest +import requests +import yaml + +from robottelo.config import settings +from robottelo.constants import LIFECYCLE_METADATA_FILE +from robottelo.logging import logger + + +@pytest.mark.tier2 +def test_positive_check_eol_date(target_sat): + """Check if the EOL date for the satellite version + + :id: 1c2f0d19-a357-4461-9ace-edb468f9ca5c + + :expectedresults: EOL date from satellite-lifecycle package is accurate + """ + current_version = '.'.join(target_sat.version.split('.')[0:2]) + output = yaml.load(target_sat.execute(rf'cat {LIFECYCLE_METADATA_FILE}').stdout, yaml.Loader) + logger.debug(f'contents of {LIFECYCLE_METADATA_FILE} :{output}') + eol_datetime = datetime.strptime(output['releases'][current_version]['end_of_life'], '%Y-%m-%d') + result = requests.get(settings.subscription.lifecycle_api_url, verify=False) + if result.status_code != 200: + result.raise_for_status() + versions = result.json()['data'][0]['versions'] + version = [v for v in versions if v['name'] == current_version] + if len(version) > 0: + api_date = [ + (p['date_format'], p['date']) + for p in version[0]['phases'] + if p['name'] == 'Maintenance support' + ] + if api_date[0][0] == 'string': + assert eol_datetime.strftime("%B, %Y") in api_date[0][1] + elif api_date[0][0] == 'date': + assert eol_datetime.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + 'Z' == api_date[0][1] + else: + pytest.fail("Unexpcted date format returned") + else: + pytest.skip("The Satellite version is not GA yet") From 83c20939c2dd6e1a5ad75b24d22286062d3a4e78 Mon Sep 17 00:00:00 2001 From: Samuel Bible Date: Mon, 3 Jun 2024 03:05:32 -0500 Subject: [PATCH 13/31] [CV Eval] CV Version Test (#14949) * Add test reading CV Version table * Add test reading CV Version table * Add additional checks for table fields * Add test reading CV Version table * Add additional checks for table fields * Fix caseimportance string * Use only REPOS constant * Read data from product to compare in table, and use smaller repo --- tests/foreman/ui/test_contentview.py | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/foreman/ui/test_contentview.py b/tests/foreman/ui/test_contentview.py index 0d203fc7707..ee7b7f2ca27 100644 --- a/tests/foreman/ui/test_contentview.py +++ b/tests/foreman/ui/test_contentview.py @@ -15,6 +15,8 @@ from fauxfactory import gen_string import pytest +from robottelo.constants import REPOS + @pytest.mark.tier2 def test_positive_create_cv(session, target_sat): @@ -36,6 +38,47 @@ def test_positive_create_cv(session, target_sat): assert session.contentview_new.search(cv)[0]['Name'] == cv +@pytest.mark.tier2 +def test_version_table_read(session, function_sca_manifest_org, target_sat): + """Able to read CV version package details, which includes the Epoch tab + + :id: fe2a87c7-f148-40f2-b11a-c209a4807016 + + :steps: + 1. Enable and Sync RHEL8 Base OS Repo + 2. Add repo to a CV + 3. Publish the CV + 4. Navigate to the published Version's page + 5. Filter packages to only an arbitrary package + + :expectedresults: The package is present, has the appropriate name, and has the epoch tab present + + :CaseImportance: Critical + + :BZ: 1911545 + + :customerscenario: true + """ + rh_repo_id = target_sat.api_factory.enable_sync_redhat_repo( + REPOS['rhae2.9_el8'], function_sca_manifest_org.id + ) + rh_repo = target_sat.api.Repository(id=rh_repo_id).read() + packages = target_sat.api.Repository(id=rh_repo_id).packages() + cv = target_sat.api.ContentView(organization=function_sca_manifest_org).create() + cv = target_sat.api.ContentView(id=cv.id, repository=[rh_repo]).update(["repository"]) + cv.publish() + with target_sat.ui_session() as session: + session.organization.select(org_name=function_sca_manifest_org.name) + response = session.contentview_new.read_version_table( + cv.name, 'Version 1.0', 'rpmPackages', search_param=packages['results'][0]['nvra'] + ) + assert response[0]['Epoch'] == packages['results'][0]['epoch'] + assert response[0]['Name'] == packages['results'][0]['nvra'] + assert response[0]['Version'] == packages['results'][0]['version'] + assert response[0]['Release'] == packages['results'][0]['release'] + assert response[0]['Arch'] == packages['results'][0]['arch'] + + @pytest.mark.tier2 def test_no_blank_page_on_language_switch(session, target_sat, module_org): """Able to view the new CV UI when the language is set to something other From d71f6ab20881193448346554802b94a194988ea2 Mon Sep 17 00:00:00 2001 From: Jameer Pathan <21165044+jameerpathan111@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:17:53 +0200 Subject: [PATCH 14/31] Add support for reporting/commenting test results to the Jira issue (#15104) --- conf/jira.yaml.template | 5 + conftest.py | 5 +- pytest_plugins/jira_comments.py | 92 +++++++++++++++++++ robottelo/config/validators.py | 4 + robottelo/utils/issue_handlers/jira.py | 122 +++++++++++++++++-------- tests/foreman/ui/test_rhc.py | 2 +- 6 files changed, 190 insertions(+), 40 deletions(-) create mode 100644 pytest_plugins/jira_comments.py diff --git a/conf/jira.yaml.template b/conf/jira.yaml.template index e76ac35f157..9c90993fe2d 100644 --- a/conf/jira.yaml.template +++ b/conf/jira.yaml.template @@ -3,3 +3,8 @@ JIRA: URL: https://issues.redhat.com # Provide api_key to access Jira REST API API_KEY: replace-with-jira-api-key + COMMENT_TYPE: group + COMMENT_VISIBILITY: "Red Hat Employee" + ENABLE_COMMENT: false + # Comment only if jira is in one of the following state + ISSUE_STATUS: ["Review", "Release Pending"] diff --git a/conftest.py b/conftest.py index f50f81e2975..cdc924e9063 100644 --- a/conftest.py +++ b/conftest.py @@ -22,6 +22,7 @@ 'pytest_plugins.requirements.update_requirements', 'pytest_plugins.sanity_plugin', 'pytest_plugins.video_cleanup', + 'pytest_plugins.jira_comments', 'pytest_plugins.capsule_n-minus', # Fixtures 'pytest_fixtures.core.broker', @@ -79,9 +80,9 @@ def pytest_runtest_makereport(item, call): # execute all other hooks to obtain the report object outcome = yield - rep = outcome.get_result() + report = outcome.get_result() # set a report attribute for each phase of a call, which can # be "setup", "call", "teardown" - setattr(item, "rep_" + rep.when, rep) + setattr(item, "report_" + report.when, report) diff --git a/pytest_plugins/jira_comments.py b/pytest_plugins/jira_comments.py new file mode 100644 index 00000000000..9e8d56a6843 --- /dev/null +++ b/pytest_plugins/jira_comments.py @@ -0,0 +1,92 @@ +from collections import defaultdict +import os + +import pytest + +from robottelo.config import settings +from robottelo.logging import logger +from robottelo.utils.issue_handlers.jira import add_comment_on_jira + + +def pytest_addoption(parser): + """Add --jira-comments option to report test results on the Jira issue.""" + help_comment = ( + 'Report/Comment test results on Jira issues. ' + 'Test results marked with "Verifies" or "BlockedBy" doc fields will be commented on the corresponding Jira issues. ' + 'Note: To prevent accidental use, users must set ENABLE_COMMENT to true in the jira.yaml configuration file.' + ) + parser.addoption( + '--jira-comments', + action='store_true', + default=False, + help=help_comment, + ) + + +def pytest_configure(config): + """Register jira_comments markers to avoid warnings.""" + config.addinivalue_line('markers', 'jira_comments: Add test result comment on Jira issue.') + pytest.jira_comments = config.getoption('jira_comments') + + +def update_issue_to_tests_map(item, marker, test_result): + """If the test has Verifies or BlockedBy doc field, + an issue to tests mapping will be added/updated in config.issue_to_tests_map + for each test run with outcome of the test. + """ + if marker: + for issue in marker.args[0]: + item.config.issue_to_tests_map[issue].append( + {'nodeid': item.nodeid, 'outcome': test_result} + ) + + +@pytest.hookimpl(trylast=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + """Create jira issue to test result mapping. Used for commenting result on Jira.""" + outcome = yield + verifies_marker = item.get_closest_marker('verifies_issues') + blocked_by_marker = item.get_closest_marker('blocked_by') + enable_jira_comments = item.config.getoption('jira_comments') + if ( + settings.jira.enable_comment + and enable_jira_comments + and (verifies_marker or blocked_by_marker) + ): + report = outcome.get_result() + if report.when == 'teardown': + test_result = ( + 'passed' + if ( + item.report_setup.passed + and item.report_call.passed + and report.outcome == 'passed' + ) + else 'failed' + ) + if not hasattr(item.config, 'issue_to_tests_map'): + item.config.issue_to_tests_map = defaultdict(list) + # Update issue_to_tests_map for Verifies testimony marker + update_issue_to_tests_map(item, verifies_marker, test_result) + # Update issue_to_tests_map for BlockedBy testimony marker + update_issue_to_tests_map(item, blocked_by_marker, test_result) + + +def pytest_sessionfinish(session, exitstatus): + """Add test result comment to related Jira issues.""" + if hasattr(session.config, 'issue_to_tests_map'): + user = os.environ.get('USER') + build_url = os.environ.get('BUILD_URL') + for issue in session.config.issue_to_tests_map: + comment_body = ( + f'This is an automated comment from job/user: {build_url if build_url else user} for a Robottelo test run.\n' + f'Satellite/Capsule: {settings.server.version.release} Snap: {settings.server.version.snap} \n' + f'Result for tests linked with issue: {issue} \n' + ) + for item in session.config.issue_to_tests_map[issue]: + comment_body += f'{item["nodeid"]} : {item["outcome"]} \n' + try: + add_comment_on_jira(issue, comment_body) + except Exception as e: + # Handle any errors in adding comments to Jira + logger.warning(f'Failed to add comment to Jira issue {issue}: {e}') diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index 971ac082d13..e0a24620b0a 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -196,6 +196,10 @@ jira=[ Validator('jira.url', default='https://issues.redhat.com'), Validator('jira.api_key', must_exist=True), + Validator('jira.comment_type', default="group"), + Validator('jira.comment_visibility', default="Red Hat Employee"), + Validator('jira.enable_comment', default=False), + Validator('jira.issue_status', default=["Review", "Release Pending"]), ], ldap=[ Validator( diff --git a/robottelo/utils/issue_handlers/jira.py b/robottelo/utils/issue_handlers/jira.py index dfeb1c966c4..0a03a538952 100644 --- a/robottelo/utils/issue_handlers/jira.py +++ b/robottelo/utils/issue_handlers/jira.py @@ -21,15 +21,15 @@ VERSION_RE = re.compile(r'(?:sat-)*?(?P\d\.\d)\.\w*') -def is_open_jira(issue, data=None): +def is_open_jira(issue_id, data=None): """Check if specific Jira is open consulting a cached `data` dict or calling Jira REST API. Arguments: - issue {str} -- The Jira reference e.g: SAT-20548 + issue_id {str} -- The Jira reference e.g: SAT-20548 data {dict} -- Issue data indexed by : or None """ - jira = try_from_cache(issue, data) + jira = try_from_cache(issue_id, data) if jira.get("is_open") is not None: # issue has been already processed return jira["is_open"] @@ -54,39 +54,39 @@ def is_open_jira(issue, data=None): return status not in JIRA_CLOSED_STATUSES and status != JIRA_ONQA_STATUS -def are_all_jira_open(issues, data=None): +def are_all_jira_open(issue_ids, data=None): """Check if all Jira is open consulting a cached `data` dict or calling Jira REST API. Arguments: - issues {list} -- The Jira reference e.g: ['SAT-20548', 'SAT-20548'] + issue_ids {list} -- The Jira reference e.g: ['SAT-20548', 'SAT-20548'] data {dict} -- Issue data indexed by : or None """ - return all(is_open_jira(issue, data) for issue in issues) + return all(is_open_jira(issue_id, data) for issue_id in issue_ids) -def are_any_jira_open(issues, data=None): +def are_any_jira_open(issue_ids, data=None): """Check if any of the Jira is open consulting a cached `data` dict or calling Jira REST API. Arguments: - issues {list} -- The Jira reference e.g: ['SAT-20548', 'SAT-20548'] + issue_ids {list} -- The Jira reference e.g: ['SAT-20548', 'SAT-20548'] data {dict} -- Issue data indexed by : or None """ - return any(is_open_jira(issue, data) for issue in issues) + return any(is_open_jira(issue_id, data) for issue_id in issue_ids) -def should_deselect_jira(issue, data=None): - """Check if test should be deselected based on marked issue. +def should_deselect_jira(issue_id, data=None): + """Check if test should be deselected based on marked issue_id. 1. Resolution "Obsolete" should deselect Arguments: - issue {str} -- The Jira reference e.g: SAT-12345 + issue_id {str} -- The Jira reference e.g: SAT-12345 data {dict} -- Issue data indexed by : or None """ - jira = try_from_cache(issue, data) + jira = try_from_cache(issue_id, data) if jira.get("is_deselected") is not None: # issue has been already processed return jira["is_deselected"] @@ -105,21 +105,21 @@ def follow_duplicates(jira): return jira -def try_from_cache(issue, data=None): +def try_from_cache(issue_id, data=None): """Try to fetch issue from given data cache or previous loaded on pytest. Arguments: - issue {str} -- The Jira reference e.g: SAT-12345 + issue_id {str} -- The Jira reference e.g: SAT-12345 data {dict} -- Issue data indexed by : or None """ try: - # issue must be passed in `data` argument or already fetched in pytest - if not data and not len(pytest.issue_data[issue]['data']): + # issue_id must be passed in `data` argument or already fetched in pytest + if not data and not len(pytest.issue_data[issue_id]['data']): raise ValueError - return data or pytest.issue_data[issue]['data'] + return data or pytest.issue_data[issue_id]['data'] except (KeyError, AttributeError, ValueError): # pragma: no cover # If not then call Jira API again - return get_single_jira(str(issue)) + return get_single_jira(str(issue_id)) def collect_data_jira(collected_data, cached_data): # pragma: no cover @@ -169,26 +169,26 @@ def collect_dupes(jira, collected_data, cached_data=None): # pragma: no cover stop=stop_after_attempt(4), # Retry 3 times before raising wait=wait_fixed(20), # Wait seconds between retries ) -def get_data_jira(jira_numbers, cached_data=None): # pragma: no cover +def get_data_jira(issue_ids, cached_data=None): # pragma: no cover """Get a list of marked Jira data and query Jira REST API. Arguments: - jira_numbers {list of str} -- ['SAT-12345', ...] + issue_ids {list of str} -- ['SAT-12345', ...] cached_data {dict} -- Cached data previous loaded from API Returns: [list of dicts] -- [{'id':..., 'status':..., 'resolution': ...}] """ - if not jira_numbers: + if not issue_ids: return [] - cached_by_call = CACHED_RESPONSES['get_data'].get(str(sorted(jira_numbers))) + cached_by_call = CACHED_RESPONSES['get_data'].get(str(sorted(issue_ids))) if cached_by_call: return cached_by_call if cached_data: - logger.debug(f"Using cached data for {set(jira_numbers)}") - if not all([f'{number}' in cached_data for number in jira_numbers]): + logger.debug(f"Using cached data for {set(issue_ids)}") + if not all([f'{number}' in cached_data for number in issue_ids]): logger.debug("There are Jira's out of cache.") return [item['data'] for _, item in cached_data.items() if 'data' in item] @@ -200,10 +200,10 @@ def get_data_jira(jira_numbers, cached_data=None): # pragma: no cover "Provide api_key or a jira_cache.json." ) # Provide default data for collected Jira's. - return [get_default_jira(number) for number in jira_numbers] + return [get_default_jira(issue_id) for issue_id in issue_ids] # No cached data so Call Jira API - logger.debug(f"Calling Jira API for {set(jira_numbers)}") + logger.debug(f"Calling Jira API for {set(issue_ids)}") jira_fields = [ "key", "summary", @@ -216,7 +216,7 @@ def get_data_jira(jira_numbers, cached_data=None): # pragma: no cover assert field not in jira_fields # Generate jql - jql = ' OR '.join([f"id = {id}" for id in jira_numbers]) + jql = ' OR '.join([f"id = {issue_id}" for issue_id in issue_ids]) response = requests.get( f"{settings.jira.url}/rest/api/latest/search/", @@ -244,31 +244,79 @@ def get_data_jira(jira_numbers, cached_data=None): # pragma: no cover for issue in data if issue is not None ] - CACHED_RESPONSES['get_data'][str(sorted(jira_numbers))] = data + CACHED_RESPONSES['get_data'][str(sorted(issue_ids))] = data return data -def get_single_jira(number, cached_data=None): # pragma: no cover +def get_single_jira(issue_id, cached_data=None): # pragma: no cover """Call Jira API to get a single Jira data and cache it""" cached_data = cached_data or {} - jira_data = CACHED_RESPONSES['get_single'].get(number) + jira_data = CACHED_RESPONSES['get_single'].get(issue_id) if not jira_data: try: - jira_data = cached_data[f"{number}"]['data'] + jira_data = cached_data[f"{issue_id}"]['data'] except (KeyError, TypeError): - jira_data = get_data_jira([str(number)], cached_data) + jira_data = get_data_jira([str(issue_id)], cached_data) jira_data = jira_data and jira_data[0] - CACHED_RESPONSES['get_single'][number] = jira_data - return jira_data or get_default_jira(number) + CACHED_RESPONSES['get_single'][issue_id] = jira_data + return jira_data or get_default_jira(issue_id) -def get_default_jira(number): # pragma: no cover +def get_default_jira(issue_id): # pragma: no cover """This is the default Jira data when it is not possible to reach Jira api""" return { - "key": number, + "key": issue_id, "is_open": True, "is_deselected": False, "status": "", "resolution": "", "error": "missing jira api_key", } + + +def add_comment_on_jira( + issue_id, + comment, + comment_type=settings.jira.comment_type, + comment_visibility=settings.jira.comment_visibility, +): + """Adds a new comment to a Jira issue. + + Arguments: + issue_id {str} -- Jira issue number, ex. SAT-12232 + comment {str} -- Comment to add on the issue. + comment_type {str} -- Type of comment to add. + comment_visibility {str} -- Comment visibility. + + Returns: + [list of dicts] -- [{'id':..., 'status':..., 'resolution': ...}] + """ + # Raise a warning if any of the following option is not set. Note: It's a xor condition. + if settings.jira.enable_comment != pytest.jira_comments: + logger.warning( + 'Jira comments are currently disabled for this run. ' + 'To enable it, please set "enable_comment" to "true" in "config/jira.yaml ' + 'and provide --jira-comment pytest option."' + ) + return None + data = try_from_cache(issue_id) + if data["status"] in settings.jira.issue_status: + logger.debug(f"Adding a new comment on {issue_id} Jira issue.") + response = requests.post( + f"{settings.jira.url}/rest/api/latest/issue/{issue_id}/comment", + json={ + "body": comment, + "visibility": { + "type": comment_type, + "value": comment_visibility, + }, + }, + headers={"Authorization": f"Bearer {settings.jira.api_key}"}, + ) + response.raise_for_status() + return response.json() + logger.warning( + f"Jira comments are currently disabled for this issue because it's in {data['status']} state. " + f"Please update issue_status in jira.conf to overide this behaviour." + ) + return None diff --git a/tests/foreman/ui/test_rhc.py b/tests/foreman/ui/test_rhc.py index b9f02dcee8f..20c602dfc5f 100644 --- a/tests/foreman/ui/test_rhc.py +++ b/tests/foreman/ui/test_rhc.py @@ -78,7 +78,7 @@ def fixture_setup_rhc_satellite( rhcloud_manifest = manifester.get_manifest() module_target_sat.upload_manifest(module_rhc_org.id, rhcloud_manifest.content) yield - if request.node.rep_call.passed: + if request.node.report_call.passed: # Enable and sync required repos repo1_id = module_target_sat.api_factory.enable_sync_redhat_repo( constants.REPOS['rhel8_aps'], module_rhc_org.id From 914c265418700faabe0315504294ee9a8e9136c0 Mon Sep 17 00:00:00 2001 From: Peter Ondrejka Date: Mon, 3 Jun 2024 18:00:34 +0200 Subject: [PATCH 15/31] marking some 6.16 feature JIRAs (#15275) --- tests/foreman/cli/test_oscap.py | 2 ++ tests/foreman/sys/test_webpack.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/foreman/cli/test_oscap.py b/tests/foreman/cli/test_oscap.py index ba9be9124e1..09aa6b00b82 100644 --- a/tests/foreman/cli/test_oscap.py +++ b/tests/foreman/cli/test_oscap.py @@ -582,6 +582,8 @@ def test_positive_associate_scap_policy_with_hostgroups(self, scap_content, modu :expectedresults: The policy is created and associated successfully. + :Verifies: SAT-19492 + :CaseImportance: Medium """ hostgroup = module_target_sat.cli_factory.hostgroup() diff --git a/tests/foreman/sys/test_webpack.py b/tests/foreman/sys/test_webpack.py index df08627d29c..5964c111e1e 100644 --- a/tests/foreman/sys/test_webpack.py +++ b/tests/foreman/sys/test_webpack.py @@ -21,6 +21,8 @@ def test_positive_webpack5(target_sat): :id: b7f3fbb2-ef4b-4634-877f-b8ea10373e04 + :Verifies: SAT-5741 + :expectedresults: There is a file "public/webpack/foreman_tasks/foreman_tasks_remoteEntry.js" when Webpack 5 has been used. It used to be "public/webpack/foreman-tasks-.js" before. """ assert ( From c6a182cc3088807cde7709a21f27e572538e1308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Gajdu=C5=A1ek?= Date: Mon, 3 Jun 2024 19:25:14 +0200 Subject: [PATCH 16/31] Remove unused functions and fix incorrect validators and remove unused ones (#15277) --- conf/vlan.yaml.template | 10 - pytest_fixtures/component/discovery.py | 11 -- robottelo/config/validators.py | 42 +--- robottelo/exceptions.py | 8 - robottelo/host_helpers/api_factory.py | 257 ------------------------- 5 files changed, 6 insertions(+), 322 deletions(-) delete mode 100644 conf/vlan.yaml.template diff --git a/conf/vlan.yaml.template b/conf/vlan.yaml.template deleted file mode 100644 index d1c7e34bb2f..00000000000 --- a/conf/vlan.yaml.template +++ /dev/null @@ -1,10 +0,0 @@ -# VLAN Networking details -VLAN_NETWORKING: - SUBNET: # SUBNET - NETMASK: # NETMASK - BRIDGE: # BRIDGE - GATEWAY: # GATEWAY - DHCP_IPAM: # DHCP|INTERNAL DB - NETWORK: # NETWORK_VALUE (mutually exclusive with bridge) - DHCP_FROM: # IP_RANGE_FROM - DHCP_TO: # IP_RANGE_TO diff --git a/pytest_fixtures/component/discovery.py b/pytest_fixtures/component/discovery.py index c9d4375484a..e05b3859f2f 100644 --- a/pytest_fixtures/component/discovery.py +++ b/pytest_fixtures/component/discovery.py @@ -29,14 +29,3 @@ def discovery_location(module_location, module_target_sat): discovery_loc = module_target_sat.update_setting('discovery_location', module_location.name) yield module_location module_target_sat.update_setting('discovery_location', discovery_loc) - - -@pytest.fixture(scope='module') -def provisioning_env(module_target_sat, discovery_org, discovery_location): - # Build PXE default template to get default PXE file - module_target_sat.cli.ProvisioningTemplate().build_pxe_default() - return module_target_sat.api_factory.configure_provisioning( - org=discovery_org, - loc=discovery_location, - os=f'Redhat {module_target_sat.cli_factory.RHELRepository().repo_data["version"]}', - ) diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index e0a24620b0a..8f6505db48d 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -179,7 +179,6 @@ 'ipa.hostname', 'ipa.username', 'ipa.password', - 'ipa.idm_password', 'ipa.basedn', 'ipa.grpbasedn', 'ipa.user', @@ -187,7 +186,6 @@ 'ipa.disabled_ipa_user', 'ipa.group_users', 'ipa.groups', - 'ipa.idm_server_ip', 'ipa.keytab_url', 'ipa.time_based_secret', must_exist=True, @@ -348,47 +346,19 @@ Validator('upgrade.rhev_capsule_ak', must_exist=False) | Validator('upgrade.capsule_ak', must_exist=False), ], - vlan_networking=[ - Validator( - 'vlan_networking.subnet', - 'vlan_networking.netmask', - 'vlan_networking.gateway', - must_exist=True, - ), - Validator('vlan_networking.dhcp_ipam', is_in=('Internal DB', 'DHCP')), - # one, and only one, of ('bridge', 'network') must be defined - ( - Validator('vlan_networking.bridge', must_exist=True) - & Validator('vlan_networking.network', must_exist=False) - ) - | ( - Validator('vlan_networking.bridge', must_exist=False) - & Validator('vlan_networking.network', must_exist=True) - ), - # both dhcp_from and dhcp_to are defined, or neither is - Validator( - 'vlan_networking.dhcp_from', - 'vlan_networking.dhcp_to', - must_exist=True, - ) - | Validator( - 'vlan_networking.dhcp_from', - 'vlan_networking.dhcp_to', - must_exist=False, - ), - ], vmware=[ Validator( - 'vmware.vcenter', + 'vmware.vcenter7.hostname', + 'vmware.vcenter7.hypervisor', + 'vmware.vcenter7.mac_address', + 'vmware.vcenter8.hostname', + 'vmware.vcenter8.hypervisor', + 'vmware.vcenter8.mac_address', 'vmware.username', 'vmware.password', 'vmware.datacenter', 'vmware.vm_name', 'vmware.image_os', - 'vmware.image_arch', - 'vmware.image_username', - 'vmware.image_password', - 'vmware.image_name', must_exist=True, ), ], diff --git a/robottelo/exceptions.py b/robottelo/exceptions.py index 057e0891cfc..882befc1619 100644 --- a/robottelo/exceptions.py +++ b/robottelo/exceptions.py @@ -9,14 +9,6 @@ class TemplateNotFoundError(Exception): """An exception to raise when Template is not available in Satellite""" -class ImproperlyConfigured(Exception): - """Indicates that Robottelo somehow is improperly configured. - - For example, if settings file can not be found or some required - configuration is not defined. - """ - - class InvalidVaultURLForOIDC(Exception): """Raised if the vault doesnt allows OIDC login""" diff --git a/robottelo/host_helpers/api_factory.py b/robottelo/host_helpers/api_factory.py index 7f748e2498f..34e77ad6bf2 100644 --- a/robottelo/host_helpers/api_factory.py +++ b/robottelo/host_helpers/api_factory.py @@ -8,7 +8,6 @@ import time from fauxfactory import gen_ipaddr, gen_mac, gen_string -from nailgun import entity_mixins from nailgun.client import request from nailgun.entity_mixins import call_entity_method_with_timeout from requests import HTTPError @@ -16,13 +15,8 @@ from robottelo.config import settings from robottelo.constants import ( DEFAULT_ARCHITECTURE, - DEFAULT_OS_SEARCH_QUERY, - DEFAULT_PTABLE, - DEFAULT_PXE_TEMPLATE, - DEFAULT_TEMPLATE, REPO_TYPE, ) -from robottelo.exceptions import ImproperlyConfigured from robottelo.host_helpers.repository_mixins import initiate_repo_helpers @@ -174,257 +168,6 @@ def one_to_one_names(self, name): """ return {f'{name}_name', f'{name}_id'} - def configure_provisioning(self, org=None, loc=None, compute=False, os=None): - """Create and configure org, loc, product, repo, cv, env. Update proxy, - domain, subnet, compute resource, provision templates and medium with - previously created entities and create a hostgroup using all mentioned - entities. - - :param str org: Default Organization that should be used in both host - discovering and host provisioning procedures - :param str loc: Default Location that should be used in both host - discovering and host provisioning procedures - :param bool compute: If False creates a default Libvirt compute resource - :param str os: Specify the os to be used while provisioning and to - associate related entities to the specified os. - :return: List of created entities that can be re-used further in - provisioning or validation procedure (e.g. hostgroup or domain) - """ - # Create new organization and location in case they were not passed - if org is None: - org = self._satellite.api.Organization().create() - if loc is None: - loc = self._satellite.api.Location(organization=[org]).create() - if settings.repos.rhel7_os is None: - raise ImproperlyConfigured('settings file is not configured for rhel os') - # Create a new Life-Cycle environment - lc_env = self._satellite.api.LifecycleEnvironment(organization=org).create() - # Create a Product, Repository for custom RHEL7 contents - product = self._satellite.api.Product(organization=org).create() - repo = self._satellite.api.Repository( - product=product, url=settings.repos.rhel7_os, download_policy='immediate' - ).create() - - # Increased timeout value for repo sync and CV publishing and promotion - try: - old_task_timeout = entity_mixins.TASK_TIMEOUT - entity_mixins.TASK_TIMEOUT = 3600 - repo.sync() - # Create, Publish and promote CV - content_view = self._satellite.api.ContentView(organization=org).create() - content_view.repository = [repo] - content_view = content_view.update(['repository']) - content_view.publish() - content_view = content_view.read() - content_view.read().version[0].promote(data={'environment_ids': lc_env.id}) - finally: - entity_mixins.TASK_TIMEOUT = old_task_timeout - # Search for existing organization puppet environment, otherwise create a - # new one, associate organization and location where it is appropriate. - environments = self._satellite.api.Environment().search( - query=dict(search=f'organization_id={org.id}') - ) - if len(environments) > 0: - environment = environments[0].read() - environment.location.append(loc) - environment = environment.update(['location']) - else: - environment = self._satellite.api.Environment( - organization=[org], location=[loc] - ).create() - - # Search for SmartProxy, and associate location - proxy = self._satellite.api.SmartProxy().search( - query={'search': f'name={settings.server.hostname}'} - ) - proxy = proxy[0].read() - if loc.id not in [location.id for location in proxy.location]: - proxy.location.append(loc) - if org.id not in [organization.id for organization in proxy.organization]: - proxy.organization.append(org) - proxy = proxy.update(['location', 'organization']) - - # Search for existing domain or create new otherwise. Associate org, - # location and dns to it - _, _, domain = settings.server.hostname.partition('.') - domain = self._satellite.api.Domain().search(query={'search': f'name="{domain}"'}) - if len(domain) == 1: - domain = domain[0].read() - domain.location.append(loc) - domain.organization.append(org) - domain.dns = proxy - domain = domain.update(['dns', 'location', 'organization']) - else: - domain = self._satellite.api.Domain( - dns=proxy, location=[loc], organization=[org] - ).create() - - # Search if subnet is defined with given network. - # If so, just update its relevant fields otherwise, - # Create new subnet - network = settings.vlan_networking.subnet - subnet = self._satellite.api.Subnet().search(query={'search': f'network={network}'}) - if len(subnet) == 1: - subnet = subnet[0].read() - subnet.domain = [domain] - subnet.location.append(loc) - subnet.organization.append(org) - subnet.dns = proxy - subnet.dhcp = proxy - subnet.tftp = proxy - subnet.discovery = proxy - subnet.ipam = 'DHCP' - subnet = subnet.update( - ['domain', 'discovery', 'dhcp', 'dns', 'location', 'organization', 'tftp', 'ipam'] - ) - else: - # Create new subnet - subnet = self._satellite.api.Subnet( - network=network, - mask=settings.vlan_networking.netmask, - domain=[domain], - location=[loc], - organization=[org], - dns=proxy, - dhcp=proxy, - tftp=proxy, - discovery=proxy, - ipam='DHCP', - ).create() - - # Search if Libvirt compute-resource already exists - # If so, just update its relevant fields otherwise, - # Create new compute-resource with 'libvirt' provider. - # compute boolean is added to not block existing test's that depend on - # Libvirt resource and use this same functionality to all CR's. - if compute is False: - resource_url = f'qemu+ssh://root@{settings.libvirt.libvirt_hostname}/system' - comp_res = [ - res - for res in self._satellite.api.LibvirtComputeResource().search() - if res.provider == 'Libvirt' and res.url == resource_url - ] - if len(comp_res) > 0: - computeresource = self._satellite.api.LibvirtComputeResource( - id=comp_res[0].id - ).read() - computeresource.location.append(loc) - computeresource.organization.append(org) - computeresource.update(['location', 'organization']) - else: - # Create Libvirt compute-resource - self._satellite.api.LibvirtComputeResource( - provider='libvirt', - url=resource_url, - set_console_password=False, - display_type='VNC', - location=[loc.id], - organization=[org.id], - ).create() - - # Get the Partition table ID - ptable = ( - self._satellite.api.PartitionTable() - .search(query={'search': f'name="{DEFAULT_PTABLE}"'})[0] - .read() - ) - if loc.id not in [location.id for location in ptable.location]: - ptable.location.append(loc) - if org.id not in [organization.id for organization in ptable.organization]: - ptable.organization.append(org) - ptable = ptable.update(['location', 'organization']) - - # Get the OS ID - if os is None: - os = ( - self._satellite.api.OperatingSystem() - .search(query={'search': DEFAULT_OS_SEARCH_QUERY})[0] - .read() - ) - else: - os_ver = os.split(' ')[1].split('.') - os = ( - self._satellite.api.OperatingSystem() - .search( - query={ - 'search': f'family="Redhat" AND major="{os_ver[0]}" ' - f'AND minor="{os_ver[1]}")' - } - )[0] - .read() - ) - - # Get the Provisioning template_ID and update with OS, Org, Location - provisioning_template = self._satellite.api.ProvisioningTemplate().search( - query={'search': f'name="{DEFAULT_TEMPLATE}"'} - ) - provisioning_template = provisioning_template[0].read() - provisioning_template.operatingsystem.append(os) - if org.id not in [organization.id for organization in provisioning_template.organization]: - provisioning_template.organization.append(org) - if loc.id not in [location.id for location in provisioning_template.location]: - provisioning_template.location.append(loc) - provisioning_template = provisioning_template.update( - ['location', 'operatingsystem', 'organization'] - ) - - # Get the PXE template ID and update with OS, Org, location - pxe_template = self._satellite.api.ProvisioningTemplate().search( - query={'search': f'name="{DEFAULT_PXE_TEMPLATE}"'} - ) - pxe_template = pxe_template[0].read() - pxe_template.operatingsystem.append(os) - if org.id not in [organization.id for organization in pxe_template.organization]: - pxe_template.organization.append(org) - if loc.id not in [location.id for location in pxe_template.location]: - pxe_template.location.append(loc) - pxe_template = pxe_template.update(['location', 'operatingsystem', 'organization']) - - # Get the arch ID - arch = ( - self._satellite.api.Architecture() - .search(query={'search': f'name="{DEFAULT_ARCHITECTURE}"'})[0] - .read() - ) - - # Update the OS to associate arch, ptable, templates - os.architecture.append(arch) - os.ptable.append(ptable) - os.provisioning_template.append(provisioning_template) - os.provisioning_template.append(pxe_template) - os = os.update(['architecture', 'provisioning_template', 'ptable']) - # kickstart_repository is the content view and lce bind repo - kickstart_repository = self._satellite.api.Repository().search( - query=dict(content_view_id=content_view.id, environment_id=lc_env.id, name=repo.name) - )[0] - # Create Hostgroup - host_group = self._satellite.api.HostGroup( - architecture=arch, - domain=domain.id, - subnet=subnet.id, - lifecycle_environment=lc_env.id, - content_view=content_view.id, - location=[loc.id], - environment=environment.id, - puppet_proxy=proxy, - puppet_ca_proxy=proxy, - content_source=proxy, - kickstart_repository=kickstart_repository, - root_pass=gen_string('alphanumeric'), - operatingsystem=os.id, - organization=[org.id], - ptable=ptable.id, - ).create() - - return { - 'host_group': host_group.name, - 'domain': domain.name, - 'environment': environment.name, - 'ptable': ptable.name, - 'subnet': subnet.name, - 'os': os.title, - } - def create_role_permissions( self, role, permissions_types_names, search=None ): # pragma: no cover From 2152e515f6bf591a8d14c953918caea0fc97ce0a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:20:07 +0530 Subject: [PATCH 17/31] [pre-commit.ci] pre-commit autoupdate (#15278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.5...v0.4.7) - [github.com/gitleaks/gitleaks: v8.18.2 → v8.18.3](https://github.com/gitleaks/gitleaks/compare/v8.18.2...v8.18.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9a8d0df7c13..8068a2860b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: check-yaml - id: debug-statements - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.5 + rev: v0.4.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -28,6 +28,6 @@ repos: types: [text] require_serial: true - repo: https://github.com/gitleaks/gitleaks - rev: v8.18.2 + rev: v8.18.3 hooks: - id: gitleaks From 65b7d284edce38fe95fcd10e42eaa9cfc98ea0ba Mon Sep 17 00:00:00 2001 From: Satellite QE <115476073+Satellite-QE@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:05:08 -0400 Subject: [PATCH 18/31] [master]: Changes for new 6.16.z branch (#15211) * Changes for new 6.16.z branch * removing the 6.12.z as it is absoute --------- Co-authored-by: GitHub Action Co-authored-by: omkarkhatavkar --- .github/dependabot.yml | 4 ++-- conf/robottelo.yaml.template | 2 +- robottelo/constants/__init__.py | 4 ++-- tests/foreman/cli/test_capsulecontent.py | 1 - tests/foreman/installer/test_installer.py | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a40fa25f8fe..e93cbd145b7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,12 +10,12 @@ updates: schedule: interval: "daily" labels: + - '6.16.z' - "CherryPick" - "dependencies" - "6.15.z" - "6.14.z" - "6.13.z" - - "6.12.z" # Maintain dependencies for our GitHub Actions - package-ecosystem: "github-actions" @@ -23,9 +23,9 @@ updates: schedule: interval: "daily" labels: + - '6.16.z' - "CherryPick" - "dependencies" - "6.15.z" - "6.14.z" - "6.13.z" - - "6.12.z" diff --git a/conf/robottelo.yaml.template b/conf/robottelo.yaml.template index 69835f6bb64..87b5926bf65 100644 --- a/conf/robottelo.yaml.template +++ b/conf/robottelo.yaml.template @@ -15,7 +15,7 @@ ROBOTTELO: RUN_ONE_DATAPOINT: false # Satellite version supported by this branch # UNDR version is used for some URL composition - SATELLITE_VERSION: "6.16" + SATELLITE_VERSION: "6.17" # The Base OS RHEL Version(x.y) where the satellite would be installed RHEL_VERSION: "8.9" # Dynaconf and Dynaconf hooks related options diff --git a/robottelo/constants/__init__.py b/robottelo/constants/__init__.py index b91b2d67ba0..ee8dc363870 100644 --- a/robottelo/constants/__init__.py +++ b/robottelo/constants/__init__.py @@ -6,9 +6,9 @@ from nailgun import entities # This should be updated after each version branch -SATELLITE_VERSION = "6.16" +SATELLITE_VERSION = "6.17" SATELLITE_OS_VERSION = "8" -SAT_NON_GA_VERSIONS = ['6.15', '6.16'] +SAT_NON_GA_VERSIONS = ['6.16', '6.17'] # Default system ports HTTPS_PORT = '443' diff --git a/tests/foreman/cli/test_capsulecontent.py b/tests/foreman/cli/test_capsulecontent.py index f0b00609b04..47441ec91db 100644 --- a/tests/foreman/cli/test_capsulecontent.py +++ b/tests/foreman/cli/test_capsulecontent.py @@ -306,7 +306,6 @@ def test_positive_update_counts(target_sat, module_capsule_configured): ) -@pytest.mark.stream @pytest.mark.parametrize('repair_type', ['repo', 'cv', 'lce']) @pytest.mark.parametrize( 'module_synced_content', diff --git a/tests/foreman/installer/test_installer.py b/tests/foreman/installer/test_installer.py index ae8ce8d3009..d562e1e5dd8 100644 --- a/tests/foreman/installer/test_installer.py +++ b/tests/foreman/installer/test_installer.py @@ -406,7 +406,6 @@ def test_positive_check_installer_hammer_ping(target_sat): assert 'ok' in line -@pytest.mark.stream @pytest.mark.upgrade @pytest.mark.tier3 @pytest.mark.build_sanity From bf0544cb1225cad12bf02cdcf144f28305d92ea1 Mon Sep 17 00:00:00 2001 From: Samuel Bible Date: Tue, 4 Jun 2024 05:08:12 -0500 Subject: [PATCH 19/31] Remove old CV UI tests (#14989) Mark all tests as stubbed --- tests/foreman/ui/test_contentview_old.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/foreman/ui/test_contentview_old.py b/tests/foreman/ui/test_contentview_old.py index 9d8bba4c6c1..dc8ea653747 100644 --- a/tests/foreman/ui/test_contentview_old.py +++ b/tests/foreman/ui/test_contentview_old.py @@ -52,6 +52,8 @@ VERSION = 'Version 1.0' +pytestmark = [pytest.mark.stubbed] + @pytest.fixture(scope='module') def module_org(module_target_sat): From a6aa7b8300a8b66a267a72f04d26c22a33a03338 Mon Sep 17 00:00:00 2001 From: Jameer Pathan <21165044+jameerpathan111@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:07:29 +0200 Subject: [PATCH 20/31] Map test_positive_clone_backup with 6.16 Jira RFE (#15287) --- tests/foreman/destructive/test_clone.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/foreman/destructive/test_clone.py b/tests/foreman/destructive/test_clone.py index b590eff7d7a..361d27e7966 100644 --- a/tests/foreman/destructive/test_clone.py +++ b/tests/foreman/destructive/test_clone.py @@ -51,6 +51,8 @@ def test_positive_clone_backup( :BZ: 2142514, 2013776 + :Verifies: SAT-10789 + :customerscenario: true """ rhel_version = sat_ready_rhel._v_major From d0baa04581a7b21719520d641692cad0e63dcc8c Mon Sep 17 00:00:00 2001 From: Jameer Pathan <21165044+jameerpathan111@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:09:46 +0200 Subject: [PATCH 21/31] Fix test selection issue for --verifies-issues option (#15297) --- pytest_plugins/metadata_markers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_plugins/metadata_markers.py b/pytest_plugins/metadata_markers.py index 1e04182279d..d61105d9924 100644 --- a/pytest_plugins/metadata_markers.py +++ b/pytest_plugins/metadata_markers.py @@ -115,7 +115,7 @@ def handle_verification_issues(item, verifies_marker, verifies_issues): verifies_args = verifies_marker.args[0] if all(issue not in verifies_issues for issue in verifies_args): log_and_deselect(item, '--verifies-issues') - return False + return False return True From 00839faa83a0a91bf2673912eade9da94cf06881 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 08:52:00 -0400 Subject: [PATCH 22/31] Bump manifester from 0.0.14 to 0.2.3 (#15264) Bumps [manifester](https://github.com/SatelliteQE/manifester) from 0.0.14 to 0.2.3. - [Release notes](https://github.com/SatelliteQE/manifester/releases) - [Commits](https://github.com/SatelliteQE/manifester/compare/v0.0.14...v0.2.3) --- updated-dependencies: - dependency-name: manifester dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ac368270c74..a473d71fd22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ deepdiff==7.0.1 dynaconf[vault]==3.2.5 fauxfactory==3.1.1 jinja2==3.1.4 -manifester==0.0.14 +manifester==0.2.3 navmazing==1.2.2 productmd==1.38 pyotp==2.9.0 From 76d055f61c6b8493bdab3f91d95ef52eabb6642e Mon Sep 17 00:00:00 2001 From: Omkar Khatavkar Date: Tue, 4 Jun 2024 22:29:47 +0530 Subject: [PATCH 23/31] updated the manifestor test for dependabot (#15309) --- .github/dependency_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependency_tests.yaml b/.github/dependency_tests.yaml index 82d24833254..9e623ea6c48 100644 --- a/.github/dependency_tests.yaml +++ b/.github/dependency_tests.yaml @@ -1,7 +1,7 @@ broker[docker]: "tests/foreman/ -k 'test_host_registration_end_to_end or test_positive_erratum_applicability or test_positive_upload_content'" deepdiff: "tests/foreman/endtoend/test_api_endtoend.py -k 'test_positive_get_links'" dynaconf[vault]: "tests/foreman/api/test_ldapauthsource.py -k 'test_positive_endtoend'" -manifester: "tests/foreman/api/test_subscription.py -k 'test_positive_create_after_refresh'" +manifester: "tests/foreman/cli/test_contentview.py -k 'test_positive_promote_rh_content'" navmazing: "tests/foreman/ui/test_repository.py -k 'test_positive_create_as_non_admin_user'" pyotp: "tests/foreman/ui/test_ldap_authentication.py -k 'test_positive_login_user_password_otp'" pytest-xdist: "tests/foreman/ -n 3 -m 'build_sanity'" From dccae5991abc7b312343949dc9853ff8ee873003 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:58:12 -0400 Subject: [PATCH 24/31] Bump cryptography from 42.0.7 to 42.0.8 (#15315) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a473d71fd22..c274778bcb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ apypie==0.4.0 betelgeuse==1.11.0 broker[docker]==0.4.9 -cryptography==42.0.7 +cryptography==42.0.8 deepdiff==7.0.1 dynaconf[vault]==3.2.5 fauxfactory==3.1.1 From 651b2cf43e909b91cbc7b28a1a1833975ca7447e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:26:17 -0400 Subject: [PATCH 25/31] Bump pytest from 8.2.1 to 8.2.2 (#15316) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c274778bcb9..24fce498782 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ navmazing==1.2.2 productmd==1.38 pyotp==2.9.0 python-box==7.1.1 -pytest==8.2.1 +pytest==8.2.2 pytest-order==1.2.1 pytest-services==2.2.1 pytest-mock==3.14.0 From 0119a0f4729ba8000bb28a65c0b808722bd2e56b Mon Sep 17 00:00:00 2001 From: Cole Higgins Date: Wed, 5 Jun 2024 04:10:14 -0400 Subject: [PATCH 26/31] [RFE} Test for upgrading satellite to 6.16 SCA only (#14476) * Test for candlepin testing in sca only * Adding post upgrade test to upgrade scenario * addressing comments * addressing comments * addressing comments quotes * fixing register call * removing extra var --- tests/upgrades/test_repository.py | 148 ++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/tests/upgrades/test_repository.py b/tests/upgrades/test_repository.py index 9b4de62f02f..ae8f672ab3f 100644 --- a/tests/upgrades/test_repository.py +++ b/tests/upgrades/test_repository.py @@ -14,6 +14,7 @@ import pytest +from robottelo import constants from robottelo.config import settings from robottelo.constants import ( DEFAULT_ARCHITECTURE, @@ -372,3 +373,150 @@ def test_post_scenario_sync_large_repo(self, target_sat, pre_upgrade_data): repo = target_sat.api.Repository(id=rh_repo_id).read() res = repo.sync(timeout=4000) assert res['result'] == 'success' + + +class TestSimpleContentAccessOnly: + """ + The scenario to test simple content access mode before and after an upgrade to a + satellite with simple content access mode only. + """ + + @pytest.mark.rhel_ver_list([7, 8, 9]) + @pytest.mark.no_containers + @pytest.mark.pre_upgrade + def test_pre_simple_content_access_only( + self, + target_sat, + save_test_data, + rhel_contenthost, + upgrade_entitlement_manifest_org, + ): + """Register host with an activation key that has one custom repository(overriden to enabled) + and one Red Hat repository enabled but no subscriptions attached. Assert that only + the Red Hat repository is enabled on the host and that the host is in entitlement mode. + + :id: preupgrade-d6661342-a348-4dd5-9ddf-3fd630b1e286 + + :steps: + 1. Before satellite upgrade + 2. Create new organization and location + 3. Upload a manifest in it + 4. Create a ak with repos and no subscriptions added to it + 5. Create a content host + 6. Register the content host + 7. Confirm content host only has red hat repo enabled + 8. Confirm content host is in entitlement mode + + :expectedresults: + 1. Content host is registered in entitlement mode and has only the red hat repository + enabled on host. + + """ + org = upgrade_entitlement_manifest_org + rhel_contenthost._skip_context_checkin = True + lce = target_sat.api.LifecycleEnvironment(organization=org).create() + product = target_sat.api.Product(organization=org).create() + custom_repo = target_sat.api.Repository( + content_type='yum', product=product, url=settings.repos.module_stream_1.url + ).create() + custom_repo.sync() + if rhel_contenthost.os_version.major > 7: + rh_repo_id = target_sat.api_factory.enable_sync_redhat_repo( + constants.REPOS[f'rhel{rhel_contenthost.os_version.major}_bos'], org.id + ) + else: + rh_repo_id = target_sat.api_factory.enable_sync_redhat_repo( + constants.REPOS[f'rhel{rhel_contenthost.os_version.major}'], org.id + ) + rh_repo = target_sat.api.Repository(id=rh_repo_id).read() + assert rh_repo.content_counts['rpm'] >= 1 + repo_list = [custom_repo, rh_repo] + content_view = target_sat.publish_content_view(org, repo_list) + content_view.version[-1].promote(data={'environment_ids': lce.id}) + subscription = target_sat.api.Subscription(organization=org.id).search( + query={'search': f'name="{constants.DEFAULT_SUBSCRIPTION_NAME}"'} + ) + assert len(subscription) + activation_key = target_sat.api.ActivationKey( + content_view=content_view, + organization=org, + environment=lce, + ).create() + all_content = activation_key.product_content(data={'content_access_mode_all': '1'})[ + 'results' + ] + for content in all_content: + if content['name'] == custom_repo.name: + custom_content_label = content['label'] + if content['repositories'][0]['id'] == rh_repo_id: + rh_content_label = content['label'] + activation_key.content_override( + data={'content_overrides': [{'content_label': custom_content_label, 'value': '1'}]} + ) + rhel_contenthost.register( + org=org, loc=None, activation_keys=activation_key.name, target=target_sat + ) + enabled = rhel_contenthost.execute('subscription-manager repos --list-enabled') + assert rh_content_label in enabled.stdout + assert custom_content_label not in enabled.stdout + sca_access = target_sat.execute( + f'echo "Organization.find({org.id}).simple_content_access?" | foreman-rake console' + ) + assert 'false' in sca_access.stdout + sca_mode = target_sat.execute( + f'echo "Organization.find({org.id}).content_access_mode" | foreman-rake console' + ) + assert 'entitlement' in sca_mode.stdout + sca_list = target_sat.execute( + f'echo "Organization.find({org.id}).content_access_mode_list" | foreman-rake console' + ) + assert 'entitlement' in sca_list.stdout + assert rhel_contenthost.subscribed + save_test_data( + { + 'rhel_client': rhel_contenthost.hostname, + 'org_id': org.id, + 'rh_content_label': rh_content_label, + 'custom_content_label': custom_content_label, + } + ) + + @pytest.mark.parametrize('pre_upgrade_data', ['rhel7', 'rhel8', 'rhel9'], indirect=True) + @pytest.mark.post_upgrade(depend_on=test_pre_simple_content_access_only) + def test_post_simple_content_access_only(self, target_sat, pre_upgrade_data): + """Check that both the custom repository and the red hat repository are enabled + and that the host is in simple content access mode. + + :id: postupgrade-6b418042-7bd7-40a6-9307-149614cc2672 + + :steps: + 1. After satellite upgrade + 2. Confirm host has both custom and red hat repository enabled + 3. Confirm host is in simple content access mode + + :expectedresults: + 1. Content host is now in simple content access mode and custom repository + is enabled on host. + """ + client_hostname = pre_upgrade_data.get('rhel_client') + org_id = pre_upgrade_data.get('org_id') + rh_content_label = pre_upgrade_data.get('rh_content_label') + custom_content_label = pre_upgrade_data.get('custom_content_label') + rhel_client = ContentHost.get_host_by_hostname(client_hostname) + enabled = rhel_client.execute('subscription-manager repos --list-enabled') + assert rh_content_label in enabled.stdout + assert custom_content_label in enabled.stdout + sca_access = target_sat.execute( + f'echo "Organization.find({org_id}).simple_content_access?" | foreman-rake console' + ) + assert 'True' in sca_access.stdout + sca_mode = target_sat.execute( + f'echo "Organization.find({org_id}).content_access_mode" | foreman-rake console' + ) + assert 'entitlement' not in sca_mode.stdout + assert 'org_environment' in sca_mode.stdout + sca_list = target_sat.execute( + f'echo "Organization.find({org_id}).content_access_mode_list" | foreman-rake console' + ) + assert 'entitlement' not in sca_list.stdout + assert 'org_environment' in sca_mode.stdout From 17aafe4193bc057accc869b576df3fb235f02220 Mon Sep 17 00:00:00 2001 From: Cole Higgins Date: Wed, 5 Jun 2024 04:21:07 -0400 Subject: [PATCH 27/31] [Reporting] Updating reporting tests to be sca compliant (#15166) * Updating reporting component to sca only * addressing comments --- tests/foreman/api/test_reporttemplates.py | 113 +----------- tests/foreman/cli/test_reporttemplates.py | 201 ++-------------------- tests/foreman/ui/test_reporttemplates.py | 89 +--------- 3 files changed, 26 insertions(+), 377 deletions(-) diff --git a/tests/foreman/api/test_reporttemplates.py b/tests/foreman/api/test_reporttemplates.py index 2dabfb8bc25..da167d8c8f0 100644 --- a/tests/foreman/api/test_reporttemplates.py +++ b/tests/foreman/api/test_reporttemplates.py @@ -14,7 +14,6 @@ import re -from broker import Broker from fauxfactory import gen_string import pytest from requests import HTTPError @@ -23,7 +22,6 @@ from robottelo.config import settings from robottelo.constants import ( DEFAULT_ARCHITECTURE, - DEFAULT_SUBSCRIPTION_NAME, FAKE_1_CUSTOM_PACKAGE, FAKE_1_CUSTOM_PACKAGE_NAME, FAKE_2_CUSTOM_PACKAGE, @@ -31,14 +29,13 @@ REPOS, REPOSET, ) -from robottelo.hosts import ContentHost from robottelo.utils.datafactory import parametrized, valid_data_list from robottelo.utils.issue_handlers import is_open @pytest.fixture(scope='module') -def setup_content(module_entitlement_manifest_org, module_target_sat): - org = module_entitlement_manifest_org +def setup_content(module_sca_manifest_org, module_target_sat): + org = module_sca_manifest_org rh_repo_id = module_target_sat.api_factory.enable_rhrepo_and_fetchid( basearch='x86_64', org_id=org.id, @@ -64,10 +61,11 @@ def setup_content(module_entitlement_manifest_org, module_target_sat): ak = module_target_sat.api.ActivationKey( content_view=cv, max_hosts=100, organization=org, environment=lce, auto_attach=True ).create() - subscription = module_target_sat.api.Subscription(organization=org).search( - query={'search': f'name="{DEFAULT_SUBSCRIPTION_NAME}"'} - )[0] - ak.add_subscriptions(data={'quantity': 1, 'subscription_id': subscription.id}) + all_content = ak.product_content(data={'content_access_mode_all': '1'})['results'] + content_label = [repo['label'] for repo in all_content if repo['name'] == custom_repo.name][0] + ak.content_override( + data={'content_overrides': [{'content_label': content_label, 'value': '1'}]} + ) return ak, org @@ -660,100 +658,6 @@ def test_negative_nonauthor_of_report_cant_download_it(): """ -@pytest.mark.tier3 -def test_positive_generate_entitlements_report(setup_content, target_sat): - """Generate a report using the Subscription - Entitlement Report template. - - :id: 722e8802-367b-4399-bcaa-949daab26632 - - :setup: Installed Satellite with Organization, Activation key, - Content View, Content Host, and Subscriptions. - - :steps: - - 1. Get - /api/report_templates/130-Subscription - Entitlement Report/generate/id/report_format - - :expectedresults: Report is generated showing all necessary information for entitlements. - - :CaseImportance: High - """ - with Broker(nick='rhel7', host_class=ContentHost) as vm: - ak, org = setup_content - result = vm.api_register( - target_sat, - organization=org, - activation_keys=[ak.name], - ) - assert result.status == 0, f'Failed to register host: {result.stderr}' - assert vm.subscribed - rt = ( - target_sat.api.ReportTemplate() - .search(query={'search': 'name="Subscription - Entitlement Report"'})[0] - .read() - ) - res = rt.generate( - data={ - "organization_id": org.id, - "report_format": "json", - "input_values": {"Days from Now": "no limit"}, - } - ) - assert res[0]['Host Name'] == vm.hostname - assert res[0]['Subscription Name'] == DEFAULT_SUBSCRIPTION_NAME - - -@pytest.mark.tier3 -def test_positive_schedule_entitlements_report(setup_content, target_sat): - """Schedule a report using the Subscription - Entitlement Report template. - - :id: 5152c518-b0da-4c27-8268-2be78289249f - - :setup: Installed Satellite with Organization, Activation key, - Content View, Content Host, and Subscriptions. - - :steps: - - 1. POST /api/report_templates/130-Subscription - Entitlement Report/schedule_report/ - - :expectedresults: Report is scheduled and contains all necessary - information for entitlements. - - :CaseImportance: High - """ - with Broker(nick='rhel7', host_class=ContentHost) as vm: - ak, org = setup_content - result = vm.api_register( - target_sat, - organization=org, - activation_keys=[ak.name], - ) - assert result.status == 0, f'Failed to register host: {result.stderr}' - assert vm.subscribed - rt = ( - target_sat.api.ReportTemplate() - .search(query={'search': 'name="Subscription - Entitlement Report"'})[0] - .read() - ) - scheduled_csv = rt.schedule_report( - data={ - 'id': f'{rt.id}-Subscription - Entitlement Report', - 'organization_id': org.id, - 'report_format': 'csv', - "input_values": {"Days from Now": "no limit"}, - } - ) - data_csv, _ = wait_for( - rt.report_data, - func_kwargs={'data': {'id': rt.id, 'job_id': scheduled_csv['job_id']}}, - fail_condition=None, - timeout=300, - delay=10, - ) - assert vm.hostname in data_csv - assert DEFAULT_SUBSCRIPTION_NAME in data_csv - - @pytest.mark.no_containers @pytest.mark.tier3 def test_positive_generate_job_report(setup_content, module_target_sat, content_hosts): @@ -811,8 +715,7 @@ def test_positive_generate_job_report(setup_content, module_target_sat, content_ 'input_values': {"job_id": job["id"]}, } ) - assert res[0]['Host'] == content_hosts[0].hostname - assert res[1]['Host'] == content_hosts[1].hostname + assert {i['Host'] for i in res} == {i.hostname for i in content_hosts} assert '/root' in res[0]['stdout'] assert '/root' in res[1]['stdout'] diff --git a/tests/foreman/cli/test_reporttemplates.py b/tests/foreman/cli/test_reporttemplates.py index 42fb511e1e6..497ae41b077 100644 --- a/tests/foreman/cli/test_reporttemplates.py +++ b/tests/foreman/cli/test_reporttemplates.py @@ -19,7 +19,6 @@ from robottelo.constants import ( DEFAULT_LOC, DEFAULT_ORG, - DEFAULT_SUBSCRIPTION_NAME, FAKE_0_CUSTOM_PACKAGE_NAME, FAKE_1_CUSTOM_PACKAGE, FAKE_1_CUSTOM_PACKAGE_NAME, @@ -34,28 +33,28 @@ @pytest.fixture(scope='module') -def local_environment(module_entitlement_manifest_org, module_target_sat): +def local_environment(module_sca_manifest_org, module_target_sat): """Create a lifecycle environment with CLI factory""" return module_target_sat.cli_factory.make_lifecycle_environment( - {'organization-id': module_entitlement_manifest_org.id} + {'organization-id': module_sca_manifest_org.id} ) @pytest.fixture(scope='module') -def local_content_view(module_entitlement_manifest_org, module_target_sat): +def local_content_view(module_sca_manifest_org, module_target_sat): """Create content view, repository, and product""" new_product = module_target_sat.cli_factory.make_product( - {'organization-id': module_entitlement_manifest_org.id} + {'organization-id': module_sca_manifest_org.id} ) new_repo = module_target_sat.cli_factory.make_repository({'product-id': new_product['id']}) module_target_sat.cli.Repository.synchronize({'id': new_repo['id']}) content_view = module_target_sat.cli_factory.make_content_view( - {'organization-id': module_entitlement_manifest_org.id} + {'organization-id': module_sca_manifest_org.id} ) module_target_sat.cli.ContentView.add_repository( { 'id': content_view['id'], - 'organization-id': module_entitlement_manifest_org.id, + 'organization-id': module_sca_manifest_org.id, 'repository-id': new_repo['id'], } ) @@ -64,9 +63,7 @@ def local_content_view(module_entitlement_manifest_org, module_target_sat): @pytest.fixture(scope='module') -def local_ak( - module_entitlement_manifest_org, local_environment, local_content_view, module_target_sat -): +def local_ak(module_sca_manifest_org, local_environment, local_content_view, module_target_sat): """Promote a content view version and create an activation key with CLI Factory""" cvv = module_target_sat.cli.ContentView.info({'id': local_content_view['id']})['versions'][0] module_target_sat.cli.ContentView.version_promote( @@ -76,26 +73,12 @@ def local_ak( { 'lifecycle-environment-id': local_environment['id'], 'content-view': local_content_view['name'], - 'organization-id': module_entitlement_manifest_org.id, + 'organization-id': module_sca_manifest_org.id, 'auto-attach': False, } ) -@pytest.fixture(scope='module') -def local_subscription(module_entitlement_manifest_org, local_ak, module_target_sat): - for subscription in module_target_sat.cli.Subscription.list( - {'organization-id': module_entitlement_manifest_org.id}, per_page=False - ): - if subscription['name'] == DEFAULT_SUBSCRIPTION_NAME: - break - module_target_sat.cli.ActivationKey.add_subscription( - {'id': local_ak['id'], 'subscription-id': subscription['id']} - ) - - return subscription - - @pytest.mark.tier2 def test_positive_report_help(module_target_sat): """hammer level of help included in test: @@ -754,161 +737,9 @@ def test_positive_generate_ansible_template(module_target_sat): assert host['name'] in [item.split(',')[1] for item in report_data.split('\n') if len(item) > 0] -@pytest.mark.no_containers -@pytest.mark.tier3 -def test_positive_generate_entitlements_report_multiple_formats( - module_sca_manifest_org, local_ak, local_subscription, rhel7_contenthost, target_sat -): - """Generate an report using the Subscription - Entitlement Report template - in html, yaml, and csv format. - - :id: f2b74916-1298-4d20-9c24-a2c2b3a3e9a9 - - :setup: Installed Satellite with Organization, Activation key, - Content View, Content Host, and Subscriptions. - - :steps: - 1. hammer report-template generate --organization '' --id '' --report-format '' - - :expectedresults: report is generated containing all the expected information - regarding entitlements. - - :BZ: 1830289 - - :parametrized: yes - - :customerscenario: true - """ - client = rhel7_contenthost - client.install_katello_ca(target_sat) - client.register_contenthost(module_sca_manifest_org.label, local_ak['name']) - assert client.subscribed - result_html = target_sat.cli.ReportTemplate.generate( - { - 'organization': module_sca_manifest_org.name, - 'name': 'Subscription - Entitlement Report', - 'report-format': 'html', - 'inputs': 'Days from Now=no limit', - } - ) - assert client.hostname in result_html - assert local_subscription['name'] in result_html - result_yaml = target_sat.cli.ReportTemplate.generate( - { - 'organization': module_sca_manifest_org.name, - 'name': 'Subscription - Entitlement Report', - 'report-format': 'yaml', - 'inputs': 'Days from Now=no limit', - } - ) - for entry in result_yaml: - if '-Name:' in entry: - assert client.hostname in entry - elif 'Subscription Name:' in entry: - assert local_subscription['name'] in entry - result_csv = target_sat.cli.ReportTemplate.generate( - { - 'organization': module_sca_manifest_org.name, - 'name': 'Subscription - Entitlement Report', - 'report-format': 'csv', - 'inputs': 'Days from Now=no limit', - } - ) - assert client.hostname in result_csv - assert local_subscription['name'] in result_csv - # BZ 1830289 - assert 'Subscription Total Quantity' in result_csv - - -@pytest.mark.tier3 -def test_positive_schedule_entitlements_report( - module_entitlement_manifest_org, local_ak, local_subscription, rhel7_contenthost, target_sat -): - """Schedule an report using the Subscription - Entitlement Report template in csv format. - - :id: 572fb387-86e0-40e2-b2df-e8ec26433610 - - - :setup: Installed Satellite with Organization, Activation key, - Content View, Content Host, and Subscriptions. - - :steps: - 1. hammer report-template schedule --organization '' --id '' --report-format '' - - - :expectedresults: report is scheduled and generated containing all the expected information - regarding entitlements. - - :parametrized: yes - """ - client = rhel7_contenthost - result = client.register( - module_entitlement_manifest_org, - None, - local_ak.name, - target_sat, - ) - assert result.status == 0, f'Failed to register host: {result.stderr}' - assert client.subscribed - scheduled_csv = target_sat.cli.ReportTemplate.schedule( - { - 'name': 'Subscription - Entitlement Report', - 'organization': module_entitlement_manifest_org.name, - 'report-format': 'csv', - 'inputs': 'Days from Now=no limit', - } - ) - data_csv = target_sat.cli.ReportTemplate.report_data( - { - 'name': 'Subscription - Entitlement Report', - 'job-id': scheduled_csv.split('\n', 1)[0].split('Job ID: ', 1)[1], - } - ) - assert client.hostname in data_csv - assert local_subscription['name'] in data_csv - - -@pytest.mark.tier3 -def test_entitlements_report_no_inputs_field( - module_entitlement_manifest_org, - module_location, - local_ak, - local_subscription, - rhel7_contenthost, - target_sat, -): - """Generate an report using the Subscription - Entitlement Report template - without passing in the 'Days from Now' argument in the inputs field, to test the - default setting - - :id: 5c4e52b9-314c-470d-9946-3d6e05c85b7e - - :steps: - 1. hammer report-template generate --organization '' --id '' --report-format '' - - :expectedresults: report is generated, and the Days From Now field isn't required - - :BZ: 1943306 - - :customerscenario: true - """ - client = rhel7_contenthost - client.register(module_entitlement_manifest_org, module_location, local_ak['name'], target_sat) - assert client.subscribed - result = target_sat.cli.ReportTemplate.generate( - { - 'organization': module_entitlement_manifest_org.name, - 'name': 'Subscription - Entitlement Report', - 'report-format': 'csv', - } - ) - # Only care that the Days from Now field isn't required, do not care about content - assert 'Subscription Total Quantity' in result - - @pytest.mark.tier3 def test_positive_generate_hostpkgcompare( - module_entitlement_manifest_org, local_ak, local_content_view, local_environment, target_sat + module_sca_manifest_org, local_ak, local_content_view, local_environment, target_sat ): """Generate 'Host - compare content hosts packages' report @@ -934,7 +765,7 @@ def test_positive_generate_hostpkgcompare( 'product': PRDS['rhel'], 'repository-set': REPOSET['rhst7'], 'repository': REPOS['rhst7']['name'], - 'organization-id': module_entitlement_manifest_org.id, + 'organization-id': module_sca_manifest_org.id, 'content-view-id': local_content_view['id'], 'lifecycle-environment-id': local_environment['id'], 'activationkey-id': local_ak['id'], @@ -944,7 +775,7 @@ def test_positive_generate_hostpkgcompare( target_sat.cli_factory.setup_org_for_a_custom_repo( { 'url': settings.repos.yum_6.url, - 'organization-id': module_entitlement_manifest_org.id, + 'organization-id': module_sca_manifest_org.id, 'content-view-id': local_content_view['id'], 'lifecycle-environment-id': local_environment['id'], 'activationkey-id': local_ak['id'], @@ -956,7 +787,7 @@ def test_positive_generate_hostpkgcompare( for client in hosts: # Create RHEL hosts via broker and register content host result = client.register( - module_entitlement_manifest_org, + module_sca_manifest_org, None, local_ak.name, target_sat, @@ -1048,7 +879,7 @@ def test_negative_generate_hostpkgcompare_nonexistent_host(module_target_sat): @pytest.mark.rhel_ver_list([7, 8, 9]) @pytest.mark.tier3 def test_positive_generate_installed_packages_report( - module_entitlement_manifest_org, + module_sca_manifest_org, local_ak, local_content_view, local_environment, @@ -1078,7 +909,7 @@ def test_positive_generate_installed_packages_report( target_sat.cli_factory.setup_org_for_a_custom_repo( { 'url': settings.repos.yum_6.url, - 'organization-id': module_entitlement_manifest_org.id, + 'organization-id': module_sca_manifest_org.id, 'content-view-id': local_content_view['id'], 'lifecycle-environment-id': local_environment['id'], 'activationkey-id': local_ak['id'], @@ -1086,7 +917,7 @@ def test_positive_generate_installed_packages_report( ) client = rhel_contenthost result = client.register( - module_entitlement_manifest_org, + module_sca_manifest_org, None, local_ak.name, target_sat, @@ -1096,7 +927,7 @@ def test_positive_generate_installed_packages_report( client.execute(f'yum -y install {FAKE_0_CUSTOM_PACKAGE_NAME} {FAKE_1_CUSTOM_PACKAGE}') result_html = target_sat.cli.ReportTemplate.generate( { - 'organization': module_entitlement_manifest_org.name, + 'organization': module_sca_manifest_org.name, 'name': 'Host - All Installed Packages', 'report-format': 'html', 'inputs': f'Hosts filter={client.hostname}', diff --git a/tests/foreman/ui/test_reporttemplates.py b/tests/foreman/ui/test_reporttemplates.py index 1a79c39e826..ecb15196c67 100644 --- a/tests/foreman/ui/test_reporttemplates.py +++ b/tests/foreman/ui/test_reporttemplates.py @@ -12,18 +12,15 @@ """ -import csv import json import os from pathlib import Path, PurePath from lxml import etree import pytest -import yaml from robottelo.config import robottelo_tmp_dir, settings from robottelo.constants import ( - DEFAULT_SUBSCRIPTION_NAME, FAKE_0_CUSTOM_PACKAGE_NAME, FAKE_1_CUSTOM_PACKAGE, PRDS, @@ -366,7 +363,7 @@ def test_positive_autocomplete(session): @pytest.mark.tier2 def test_positive_schedule_generation_and_get_mail( - session, module_entitlement_manifest_org, module_location, target_sat + session, module_sca_manifest_org, module_location, target_sat ): """Schedule generating a report. Request the result be sent via e-mail. @@ -421,9 +418,7 @@ def test_positive_schedule_generation_and_get_mail( target_sat.get(remote_path=str(gzip_path), local_path=str(local_gzip_file)) assert os.system(f'gunzip {local_gzip_file}') == 0 data = json.loads(local_file.read_text()) - subscription_search = target_sat.api.Subscription( - organization=module_entitlement_manifest_org - ).search() + subscription_search = target_sat.api.Subscription(organization=module_sca_manifest_org).search() assert len(data) >= len(subscription_search) > 0 keys_expected = [ 'Account number', @@ -479,86 +474,6 @@ def test_negative_nonauthor_of_report_cant_download_it(session): """ -@pytest.mark.tier3 -@pytest.mark.no_containers -def test_positive_gen_entitlements_reports_multiple_formats( - session, module_setup_content, rhel7_contenthost, target_sat -): - """Generate reports using the Subscription - Entitlement Report template - in html, yaml, json, and csv format. - - :id: b268663d-c213-4e59-8f81-61bec0838b1e - - - :setup: Installed Satellite with Organization, Activation key, - Content View, Content Host, and Subscriptions. - - :steps: - 1. Monitor -> Report Templates - 2. Subscription - Entitlement Report -> Generate - 3. Click the dropdown to select output format - 4. Submit - - :expectedresults: Reports are generated containing all the expected information - regarding Entitlements for each output format. - - :parametrized: yes - - :CaseImportance: High - """ - client = rhel7_contenthost - client.install_katello_ca(target_sat) - org, ak, _, _ = module_setup_content - org.sca_disable() - subscription = target_sat.api.Subscription(organization=org).search( - query={'search': f'name="{DEFAULT_SUBSCRIPTION_NAME}"'} - )[0] - ak.add_subscriptions(data={'quantity': 1, 'subscription_id': subscription.id}) - client.register(org.label, None, ak.name, target_sat) - assert client.subscribed - with session: - session.location.select('Default Location') - result_json = session.reporttemplate.generate( - 'Subscription - Entitlement Report', values={'output_format': 'JSON'} - ) - with open(result_json) as json_file: - data_json = json.load(json_file) - assert any(entitlement['Host Name'] == client.hostname for entitlement in data_json) - assert any( - entitlement['Subscription Name'] == DEFAULT_SUBSCRIPTION_NAME - for entitlement in data_json - ) - result_yaml = session.reporttemplate.generate( - 'Subscription - Entitlement Report', values={'output_format': 'YAML'} - ) - with open(result_yaml) as yaml_file: - data_yaml = yaml.load(yaml_file, Loader=yaml.FullLoader) - assert any(entitlement['Host Name'] == client.hostname for entitlement in data_yaml) - assert any( - entitlement['Subscription Name'] == DEFAULT_SUBSCRIPTION_NAME - for entitlement in data_yaml - ) - result_csv = session.reporttemplate.generate( - 'Subscription - Entitlement Report', values={'output_format': 'CSV'} - ) - with open(result_csv) as csv_file: - data_csv = csv.DictReader(csv_file) - items = list(data_csv) - assert any(entitlement['Host Name'] == client.hostname for entitlement in items) - assert any( - entitlement['Subscription Name'] == DEFAULT_SUBSCRIPTION_NAME for entitlement in items - ) - result_html = session.reporttemplate.generate( - 'Subscription - Entitlement Report', values={'output_format': 'HTML'} - ) - with open(result_html) as html_file: - parser = etree.HTMLParser() - tree = etree.parse(html_file, parser) - tree_result = etree.tostring(tree.getroot(), pretty_print=True, method='html').decode() - assert client.hostname in tree_result - assert DEFAULT_SUBSCRIPTION_NAME in tree_result - - @pytest.mark.rhel_ver_list([7, 8, 9]) @pytest.mark.tier3 def test_positive_generate_all_installed_packages_report( From 1dd567cd6a5f8683fb58b2f35facfe046d353023 Mon Sep 17 00:00:00 2001 From: vsedmik <46570670+vsedmik@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:40:24 +0200 Subject: [PATCH 28/31] Add a couple of fixes for PIT (#15314) --- tests/foreman/api/test_errata.py | 2 +- tests/foreman/api/test_subscription.py | 5 ++++- tests/foreman/cli/test_contentaccess.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/foreman/api/test_errata.py b/tests/foreman/api/test_errata.py index 8fd717b44fd..3d0bd7b2f84 100644 --- a/tests/foreman/api/test_errata.py +++ b/tests/foreman/api/test_errata.py @@ -659,6 +659,7 @@ def test_positive_install_in_hc( @pytest.mark.rhel_ver_match('[^6]') @pytest.mark.no_containers @pytest.mark.e2e +@pytest.mark.pit_client def test_positive_install_multiple_in_host( target_sat, rhel_contenthost, module_org, activation_key, module_lce ): @@ -1377,7 +1378,6 @@ def errata_host_lce(module_sca_manifest_org, target_sat): @pytest.mark.tier3 @pytest.mark.upgrade -@pytest.mark.pit_client @pytest.mark.no_containers @pytest.mark.rhel_ver_match('8') def test_errata_installation_with_swidtags( diff --git a/tests/foreman/api/test_subscription.py b/tests/foreman/api/test_subscription.py index 9cc697aa556..769fdcb4bed 100644 --- a/tests/foreman/api/test_subscription.py +++ b/tests/foreman/api/test_subscription.py @@ -294,7 +294,10 @@ def test_sca_end_to_end( content_view.publish() assert len(content_view.repository) == 2 host = rhel_contenthost.nailgun_host - host.content_facet_attributes = {'content_view_id': content_view.id} + host.content_facet_attributes = { + 'content_view_id': content_view.id, + 'lifecycle_environment_id': module_ak.environment.id, + } host.update(['content_facet_attributes']) rhel_contenthost.run('subscription-manager repos --enable *') repos = rhel_contenthost.run('subscription-manager refresh && yum repolist') diff --git a/tests/foreman/cli/test_contentaccess.py b/tests/foreman/cli/test_contentaccess.py index bb50180d305..19bc73bbe69 100644 --- a/tests/foreman/cli/test_contentaccess.py +++ b/tests/foreman/cli/test_contentaccess.py @@ -92,7 +92,7 @@ def vm( ) host_id = host[0].id host_content = module_target_sat.api.Host(id=host_id).read_json() - assert host_content["subscription_status"] == 5 + assert host_content['subscription_facet_attributes']['uuid'] rhel7_contenthost_module.install_katello_host_tools() return rhel7_contenthost_module From ede06b2cc9e16e1e7c8373042c22db99d082f3f5 Mon Sep 17 00:00:00 2001 From: Samuel Bible Date: Wed, 5 Jun 2024 10:55:24 -0500 Subject: [PATCH 29/31] CC Automation - reclaim-space CLI test (#14425) * Add test for reclaim-space bz * Add test for reclaim-space bz * change test name * Change to non-sca org * change test name * Change to non-sca org --- robottelo/cli/repository.py | 6 ++++ tests/foreman/cli/test_repositories.py | 38 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/robottelo/cli/repository.py b/robottelo/cli/repository.py index 342851e68fc..a04000913df 100644 --- a/robottelo/cli/repository.py +++ b/robottelo/cli/repository.py @@ -80,6 +80,12 @@ def upload_content(cls, options): cls.command_sub = 'upload-content' return cls.execute(cls._construct_command(options), output_format='csv', ignore_stderr=True) + @classmethod + def reclaim_space(cls, options): + """Remove disk space from a synced repository""" + cls.command_sub = 'reclaim-space' + return cls.execute(cls._construct_command(options), output_format='csv', ignore_stderr=True) + @classmethod def verify_checksum(cls, options): """Verify checksum of repository contents.""" diff --git a/tests/foreman/cli/test_repositories.py b/tests/foreman/cli/test_repositories.py index f7f4fdafdb9..f772794d4dc 100644 --- a/tests/foreman/cli/test_repositories.py +++ b/tests/foreman/cli/test_repositories.py @@ -125,3 +125,41 @@ def test_positive_disable_rh_repo_with_basearch(module_target_sat, module_entitl } ) assert 'Repository disabled' in disabled_repo[0]['message'] + + +def test_reclaim_space_command_no_exception(module_target_sat, module_sca_manifest_org): + """Hammer repository reclaim-space should not throw any improper exceptions + + :id: 74b669d8-ee6b-4fc6-864f-91410d7ea3c2 + + :steps: + 1. Enable and sync an On Demand repo + + 2. hammer repository reclaim-space --id REPOID --organization-id ORGID + + + :expectedresults: Command works as expected + + :customerscenario: true + + :BZ: 2164997 + """ + rh_repo_id = module_target_sat.api_factory.enable_rhrepo_and_fetchid( + basearch=DEFAULT_ARCHITECTURE, + org_id=module_sca_manifest_org.id, + product=REPOS['kickstart']['rhel8_aps']['product'], + repo=REPOS['kickstart']['rhel8_aps']['name'], + reposet=REPOS['kickstart']['rhel8_aps']['reposet'], + releasever=REPOS['kickstart']['rhel8_aps']['version'], + ) + repo = module_target_sat.api.Repository(id=rh_repo_id).read() + repo.sync(timeout=600) + output = module_target_sat.cli.Repository.reclaim_space( + { + 'organization-id': module_sca_manifest_org.id, + 'id': rh_repo_id, + } + ) + # Checking that the fail message isn't present. On a success, no message is returned + if output != {}: + assert 'Could not reclaim the repository' not in output[0]['message'] From e918739b1e2d3614d3c2d9919a9728f21115e1c9 Mon Sep 17 00:00:00 2001 From: dosas Date: Thu, 6 Jun 2024 00:17:45 +0200 Subject: [PATCH 30/31] Poll ncat tunnel pid (#15056) when checking the pid right after ncat startup it could happen that no pid was found despite ncat being started. Polling solves this problem --- robottelo/host_helpers/satellite_mixins.py | 52 +++++++++++++++------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/robottelo/host_helpers/satellite_mixins.py b/robottelo/host_helpers/satellite_mixins.py index 8de2548a0b8..d73820839bd 100644 --- a/robottelo/host_helpers/satellite_mixins.py +++ b/robottelo/host_helpers/satellite_mixins.py @@ -6,6 +6,7 @@ import re import requests +from wait_for import TimedOutError, wait_for from robottelo.cli.proxy import CapsuleTunnelError from robottelo.config import settings @@ -281,26 +282,45 @@ def default_url_on_new_port(self, oldport, newport): :rtype: str """ - pre_ncat_procs = self.execute('pgrep ncat').stdout.splitlines() - with self.session.shell() as channel: - # if ncat isn't backgrounded, it prevents the channel from closing - command = f'ncat -kl -p {newport} -c "ncat {self.hostname} {oldport}" &' - logger.debug(f'Creating tunnel: {command}') - channel.send(command) + + def check_ncat_startup(pre_ncat_procs): post_ncat_procs = self.execute('pgrep ncat').stdout.splitlines() ncat_pid = set(post_ncat_procs).difference(set(pre_ncat_procs)) - if not len(ncat_pid): - err = channel.get_exit_signal() - logger.debug(f'Tunnel failed: {err}') - # Something failed, so raise an exception. - raise CapsuleTunnelError(f'Starting ncat failed: {err}') + if len(ncat_pid): + return ncat_pid + + return None + + def start_ncat(): + pre_ncat_procs = self.execute('pgrep ncat').stdout.splitlines() + with self.session.shell() as channel: + # if ncat isn't backgrounded, it prevents the channel from closing + command = f'ncat -kl -p {newport} -c "ncat {self.hostname} {oldport}" &' + logger.debug(f'Creating tunnel: {command}') + channel.send(command) + + try: + return wait_for( + check_ncat_startup, + func_args=[pre_ncat_procs], + fail_condition=None, + timeout=5, + delay=0.5, + )[0] + except TimedOutError as e: + err = channel.get_exit_signal() + logger.debug(f'Tunnel failed: {err}') + # Something failed, so raise an exception. + raise CapsuleTunnelError(f'Starting ncat failed: {err}') from e + + ncat_pid = start_ncat() + try: forward_url = f'https://{self.hostname}:{newport}' logger.debug(f'Yielding capsule forward port url: {forward_url}') - try: - yield forward_url - finally: - logger.debug(f'Killing ncat pid: {ncat_pid}') - self.execute(f'kill {ncat_pid.pop()}') + yield forward_url + finally: + logger.debug(f'Killing ncat pid: {ncat_pid}') + self.execute(f'kill {ncat_pid.pop()}') def validate_pulp_filepath( self, From f19655de434cc45db96ab452513a201f6c2877f7 Mon Sep 17 00:00:00 2001 From: David Moore <109112035+damoore044@users.noreply.github.com> Date: Thu, 6 Jun 2024 02:54:51 -0400 Subject: [PATCH 31/31] [Stream] Remove API::Errata with SWID tags case (#15328) remove swid tags case --- tests/foreman/api/test_errata.py | 165 ------------------------------- 1 file changed, 165 deletions(-) diff --git a/tests/foreman/api/test_errata.py b/tests/foreman/api/test_errata.py index 3d0bd7b2f84..ea790e58d99 100644 --- a/tests/foreman/api/test_errata.py +++ b/tests/foreman/api/test_errata.py @@ -1346,177 +1346,12 @@ def test_positive_incremental_update_required( assert 'next_version' in response[0], 'Incremental update should be suggested at this point' -def _run_remote_command_on_content_host(command, vm, return_result=False): - result = vm.run(command) - assert result.status == 0 - if return_result: - return result.stdout - return None - - -def _set_prerequisites_for_swid_repos(vm): - _run_remote_command_on_content_host( - f'curl --insecure --remote-name {settings.repos.swid_tools_repo}', vm - ) - _run_remote_command_on_content_host('mv *swid*.repo /etc/yum.repos.d', vm) - _run_remote_command_on_content_host('yum install -y swid-tools', vm) - _run_remote_command_on_content_host('yum install -y dnf-plugin-swidtags', vm) - - -def _validate_swid_tags_installed(vm, module_name): - result = _run_remote_command_on_content_host( - f"swidq -i -n {module_name} | grep 'Name'", vm, return_result=True - ) - assert module_name in result - - @pytest.fixture def errata_host_lce(module_sca_manifest_org, target_sat): """Create and return a new lce in module SCA org.""" return target_sat.api.LifecycleEnvironment(organization=module_sca_manifest_org).create() -@pytest.mark.tier3 -@pytest.mark.upgrade -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('8') -def test_errata_installation_with_swidtags( - module_sca_manifest_org, - rhel_contenthost, - errata_host_lce, - target_sat, -): - """Verify errata installation with swid_tags and swid tags get updated after - module stream update. - - :id: 43a59b9a-eb9b-4174-8b8e-73d923b1e51e - - :setup: - - 1. rhel8 contenthost checked out, using org with simple content access. - 2. create satellite repositories having rhel8 baseOS, prereqs, custom content w/ swid tags. - 3. associate repositories to org, lifecycle environment, and cv. Sync all content. - 4. publish & promote to environment, content view version with all content. - 5. create activation key, for registering host to cv. - - :steps: - - 1. register host using cv's activation key, assert succeeded. - 2. install swid-tools, dnf-plugin-swidtags packages on content host. - 3. install older module stream and generate errata, swid tag. - 4. assert errata count, swid tags are generated. - 5. install errata via updating module stream. - 6. assert errata count and swid tag changed after module update. - - :expectedresults: - swid tags should get updated after errata installation via module stream update - - :CaseAutomation: Automated - - :parametrized: yes - - :CaseImportance: Critical - - """ - module_name = 'kangaroo' - version = '20180704111719' - org = module_sca_manifest_org - lce = errata_host_lce - cv = target_sat.api.ContentView( - organization=org, - environment=[lce], - ).create() - - repos = { - 'base_os': settings.repos.rhel8_os.baseos, # base rhel8 - 'sat_tools': settings.repos.rhel8_os.appstream, # swid prereqs - 'swid_tags': settings.repos.swid_tag.url, # module stream pkgs and errata - } - # associate repos with sat, org, lce, cv, and sync - for r in repos: - target_sat.cli_factory.setup_org_for_a_custom_repo( - { - 'url': repos[r], - 'organization-id': org.id, - 'lifecycle-environment-id': lce.id, - 'content-view-id': cv.id, - }, - ) - # promote newest cv version with all repos/content - cv = cv_publish_promote( - sat=target_sat, - org=org, - cv=cv, - lce=lce, - )['content-view'] - # ak in env, tied to content-view - ak = target_sat.api.ActivationKey( - organization=org, - environment=lce, - content_view=cv, - ).create() - # register host with ak, succeeds - result = rhel_contenthost.register( - activation_keys=ak.name, - target=target_sat, - org=org, - loc=None, - ) - assert result.status == 0, f'Failed to register the host {target_sat.hostname},\n{result}' - assert ( - rhel_contenthost.subscribed - ), f'Failed to subscribe the host {target_sat.hostname}, to content.' - result = rhel_contenthost.execute(r'subscription-manager repos --enable \*') - assert result.status == 0, f'Failed to enable repositories with subscription-manager,\n{result}' - - # install outdated module stream package - _set_prerequisites_for_swid_repos(rhel_contenthost) - result = rhel_contenthost.execute(f'dnf -y module install {module_name}:0:{version}') - assert ( - result.status == 0 - ), f'Failed to install module stream package: {module_name}:0:{version}.\n{result.stdout}' - # recalculate errata after install of old module stream - rhel_contenthost.execute('subscription-manager repos') - - # validate swid tags Installed - result = rhel_contenthost.execute( - f'swidq -i -n {module_name} | grep "File" | grep -o "rpm-.*.swidtag"', - ) - assert ( - result.status == 0 - ), f'An error occured trying to fetch swid tags for {module_name}.\n{result}' - before_errata_apply_result = result.stdout - assert before_errata_apply_result != '', f'Found no swid tags contained in {module_name}.' - assert (app_errata_count := rhel_contenthost.applicable_errata_count) == 1, ( - f'Found {rhel_contenthost.applicable_errata_count} applicable errata,' - f' after installing {module_name}:0:{version}, expected 1.' - ) - - # apply modular errata - result = rhel_contenthost.execute(f'dnf -y module update {module_name}') - assert ( - result.status == 0 - ), f'Failed to update module stream package: {module_name}.\n{result.stdout}' - assert rhel_contenthost.execute('dnf -y upload-profile').status == 0 - - # recalculate and check errata after modular update - rhel_contenthost.execute('subscription-manager repos') - app_errata_count -= 1 - assert rhel_contenthost.applicable_errata_count == app_errata_count, ( - f'Found {rhel_contenthost.applicable_errata_count} applicable errata, after modular update of {module_name},' - f' expected {app_errata_count}.' - ) - # swidtags were updated based on package version - result = rhel_contenthost.execute( - f'swidq -i -n {module_name} | grep "File" | grep -o "rpm-.*.swidtag"', - ) - assert ( - result.status == 0 - ), f'An error occured trying to fetch swid tags for {module_name}.\n{result}' - after_errata_apply_result = result.stdout - assert before_errata_apply_result != after_errata_apply_result - - @pytest.fixture(scope='module') def rh_repo_module_manifest(module_sca_manifest_org, module_target_sat): """Use module manifest org, creates tools repo, syncs and returns RH repo."""