diff --git a/.github/workflows/auto_cherry_pick.yml b/.github/workflows/auto_cherry_pick.yml index 953d403df87..301d9181b9d 100644 --- a/.github/workflows/auto_cherry_pick.yml +++ b/.github/workflows/auto_cherry_pick.yml @@ -76,7 +76,7 @@ jobs: - name: Add Parent PR's PRT comment to Auto_Cherry_Picked PR's id: add-parent-prt-comment - if: ${{ always() && steps.cherrypick.outcome == 'success' }} + if: ${{ always() && needs.find-the-parent-prt-comment.outputs.prt_comment != '' && steps.cherrypick.outcome == 'success' }} uses: thollander/actions-comment-pull-request@v2 with: message: | @@ -98,17 +98,24 @@ jobs: }) - name: Check if cherrypick pr is created - uses: juliangruber/find-pull-request-action@v1 - id: fpr + id: search_pr if: always() - with: - branch: "cherry-pick-${{ matrix.label }}-${{ github.sha }}" - base: ${{ matrix.label }} + run: | + PR_TITLE="[${{ matrix.label }}] ${{ env.title }}" + API_URL="https://api.github.com/repos/${{ github.repository }}/pulls?state=open" + PR_SEARCH_RESULT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$API_URL" | jq --arg title "$PR_TITLE" '.[] | select(.title == $title)') + if [ -n "$PR_SEARCH_RESULT" ]; then + echo "pr_found=true" >> $GITHUB_OUTPUT + echo "PR is Found with title $PR_TITLE" + else + echo "pr_found=false" >> $GITHUB_OUTPUT + echo "PR is not Found with title $PR_TITLE" + fi - ## Failure Logging to issues and GChat Group + ## Failure Logging to issues - name: Create Github issue on cherrypick failure id: create-issue - if: ${{ always() && steps.fpr.outputs.number != '' && steps.cherrypick.outcome != 'success' && startsWith(matrix.label, '6.') && matrix.label != github.base_ref }} + if: ${{ always() && steps.search_pr.outputs.pr_found == 'false' && steps.cherrypick.outcome != 'success' && startsWith(matrix.label, '6.') && matrix.label != github.base_ref }} uses: dacbd/create-issue-action@main with: token: ${{ secrets.CHERRYPICK_PAT }} diff --git a/.github/workflows/prt_result.yml b/.github/workflows/prt_result.yml new file mode 100644 index 00000000000..93227fcf272 --- /dev/null +++ b/.github/workflows/prt_result.yml @@ -0,0 +1,84 @@ +### The prt result workflow triggered through dispatch request from CI +name: post-prt-result + +# Run on workflow dispatch from CI +on: + workflow_dispatch: + inputs: + pr_number: + type: string + description: pr number for PRT run + build_number: + type: string + description: build number for PRT run + pytest_result: + type: string + description: pytest summary result line + build_status: + type: string + description: status of jenkins build e.g. success, unstable or error + prt_comment: + type: string + description: prt pytest comment triggered the PRT checks + + +jobs: + post-the-prt-result: + runs-on: ubuntu-latest + + steps: + - name: Add last PRT result into the github comment + id: add-prt-comment + if: ${{ always() && github.event.inputs.pytest_result != '' }} + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + **PRT Result** + ``` + Build Number: ${{ github.event.inputs.build_number }} + Build Status: ${{ github.event.inputs.build_status }} + PRT Comment: ${{ github.event.inputs.prt_comment }} + Test Result : ${{ github.event.inputs.pytest_result }} + ``` + pr_number: ${{ github.event.inputs.pr_number }} + GITHUB_TOKEN: ${{ secrets.CHERRYPICK_PAT }} + + - name: Add the PRT passed/failed labels + id: prt-status + if: ${{ always() && github.event.inputs.build_status != '' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.CHERRYPICK_PAT }} + script: | + const prNumber = ${{ github.event.inputs.pr_number }}; + const buildStatus = "${{ github.event.inputs.build_status }}"; + const labelToAdd = buildStatus === "SUCCESS" ? "PRT-Passed" : "PRT-Failed"; + github.rest.issues.addLabels({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [labelToAdd] + }); + - name: Remove failed label on test pass or vice-versa + if: ${{ always() && github.event.inputs.build_status != '' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.CHERRYPICK_PAT }} + script: | + const prNumber = ${{ github.event.inputs.pr_number }}; + const issue = await github.rest.issues.get({ + owner: context.issue.owner, + repo: context.issue.repo, + issue_number: prNumber, + }); + const buildStatus = "${{ github.event.inputs.build_status }}"; + const labelToRemove = buildStatus === "SUCCESS" ? "PRT-Failed" : "PRT-Passed"; + const labelExists = issue.data.labels.some(({ name }) => name === labelToRemove); + if (labelExists) { + github.rest.issues.removeLabel({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + name: [labelToRemove] + }); + } diff --git a/robottelo/hosts.py b/robottelo/hosts.py index 580c64fb3ef..d89b3db2c63 100644 --- a/robottelo/hosts.py +++ b/robottelo/hosts.py @@ -983,7 +983,7 @@ def update_known_hosts(self, ssh_key_name, host, user=None): f'Failed to put hostname in ssh known_hosts files:\n{result.stderr}' ) - def configure_puppet(self, proxy_hostname=None): + def configure_puppet(self, proxy_hostname=None, run_puppet_agent=True): """Configures puppet on the virtual machine/Host. :param proxy_hostname: external capsule hostname :return: None. @@ -1023,12 +1023,14 @@ def configure_puppet(self, proxy_hostname=None): self.execute('/opt/puppetlabs/bin/puppet agent -t') proxy_host = Host(hostname=proxy_hostname) proxy_host.execute(f'puppetserver ca sign --certname {cert_name}') - # This particular puppet run would create the host entity under - # 'All Hosts' and let's redirect stderr to /dev/null as errors at - # this stage can be ignored. - result = self.execute('/opt/puppetlabs/bin/puppet agent -t 2> /dev/null') - if result.status: - raise ContentHostError('Failed to configure puppet on the content host') + + if run_puppet_agent: + # This particular puppet run would create the host entity under + # 'All Hosts' and let's redirect stderr to /dev/null as errors at + # this stage can be ignored. + result = self.execute('/opt/puppetlabs/bin/puppet agent -t 2> /dev/null') + if result.status: + raise ContentHostError('Failed to configure puppet on the content host') def execute_foreman_scap_client(self, policy_id=None): """Executes foreman_scap_client on the vm to create security audit report. diff --git a/tests/foreman/api/test_errata.py b/tests/foreman/api/test_errata.py index 2bf7fbe2112..5bafb65d9b2 100644 --- a/tests/foreman/api/test_errata.py +++ b/tests/foreman/api/test_errata.py @@ -19,6 +19,7 @@ from robottelo.config import settings from robottelo.constants import ( DEFAULT_ARCHITECTURE, + DEFAULT_CV, FAKE_1_CUSTOM_PACKAGE, FAKE_2_CUSTOM_PACKAGE, FAKE_2_CUSTOM_PACKAGE_NAME, @@ -132,6 +133,11 @@ def _validate_errata_counts(host, errata_type, expected_value, timeout=120): ) +def _fetch_library_environment_for_org(sat, org): + search = {'search': f'name="Library" and organization_id={org.id}'} + return sat.api.LifecycleEnvironment().search(query=search)[0].read() + + def _fetch_available_errata(host, expected_amount=None, timeout=120): """Fetch available errata for host.""" errata = host.errata() @@ -414,40 +420,64 @@ def package_applicability_changed_as_expected( return True -def cv_publish_promote(sat, org, cv, lce=None, needs_publish=True): +def cv_publish_promote(sat, org, cv, lce=None, needs_publish=True, force=False): """Publish & promote Content View Version with all content visible in org. :param lce: if None, default to 'Library', - pass a single instance of lce, or list of instances. + pass a single environment :id or instance. + Or pass a list of environment :ids or isntances. :param bool needs_publish: if False, skip publish of a new version :return dictionary: 'content-view': instance of updated cv 'content-view-version': instance of newest cv version """ # Default to 'Library' lce, if None passed - # Take a single instance of lce, or list of instances - lce_ids = 'Library' + # Take a single lce id, of list of ids + # or Take a single instance of lce, or list of instances + lce_library = _fetch_library_environment_for_org(sat, org) + lce_ids = [lce_library.id] if lce is not None: if not isinstance(lce, list): - lce_ids = [lce.id] + # a list was not passed, just single id or lce + lce_ids = [lce] if isinstance(lce, int) else [lce.id] else: - lce_ids = [_lce.id for _lce in lce] + # a list of ids or instances was passed + lce_ids = lce if isinstance(lce[0], int) else [env.id for env in lce] + + for _id in lce_ids: + # entries in list of ids now just be int + assert isinstance(_id, int) + # multiple ids in list, sort: + if len(lce_ids) > 1: + lce_ids = sorted(lce_ids) + + # Publish by default, or skip and use latest existing version if needs_publish is True: - _publish_and_wait(sat, org, cv) + _publish_and_wait(sat, org, cv, search_rate=5, max_tries=12) # Content-view must have at least one published version cv = sat.api.ContentView(id=cv.id).read() assert cv.version, f'No version(s) are published to the Content-View: {cv.id}' # Find highest version id, will be the latest cvv_id = max(cvv.id for cvv in cv.version) - # Promote to lifecycle-environment(s) - if lce_ids == 'Library': - library_lce = cv.environment[0].read() + # Promote to lifecycle-environment(s): + # when we promote to other environments, 'Library'/:id should not be passed, + # as it will be promoted to automatically. + if len(lce_ids) > 1 and lce_library.id in lce_ids: + # remove library.id from list if list contains more than just library + lce_ids.remove(lce_library.id) + if lce is None or (len(lce_ids) == 1 and lce_library.id in lce_ids): + # only Library in list, or lce passed was None, promote only to Library, + # promoting out of order may require force to bypass + lce_library = lce_library.read() sat.api.ContentViewVersion(id=cvv_id).promote( - data={'environment_ids': library_lce.id, 'force': 'True'} + data={'environment_ids': [lce_library.id], 'force': force} ) else: - sat.api.ContentViewVersion(id=cvv_id).promote(data={'environment_ids': lce_ids}) + # promote to any environment ids remaining in list + sat.api.ContentViewVersion(id=cvv_id).promote( + data={'environment_ids': lce_ids, 'force': force} + ) _result = { 'content-view': sat.api.ContentView(id=cv.id).read(), 'content-view-version': sat.api.ContentViewVersion(id=cvv_id).read(), @@ -458,18 +488,22 @@ def cv_publish_promote(sat, org, cv, lce=None, needs_publish=True): return _result -def _publish_and_wait(sat, org, cv): - """Publish a new version of content-view to organization, wait for task(s) completion.""" +def _publish_and_wait(sat, org, cv, search_rate=1, max_tries=10): + """Publish a new version of content-view to organization, wait for task(s) completion. + + :param int: search_rate: time (seconds) in between each search for finished task(s). + :param int: max_tries: number of searches to perform before timing out. + """ task_id = sat.api.ContentView(id=cv.id).publish({'id': cv.id, 'organization': org})['id'] assert task_id, f'No task was invoked to publish the Content-View: {cv.id}.' # Should take < 1 minute, check in 5s intervals sat.wait_for_tasks( search_query=(f'label = Actions::Katello::ContentView::Publish and id = {task_id}'), - search_rate=5, - max_tries=12, + search_rate=search_rate, + max_tries=max_tries, ), ( f'Failed to publish the Content-View: {cv.id}, in time.' - f'Task: {task_id} failed, or timed out (60s).' + f'Task: {task_id} failed, or timed out ({search_rate*max_tries}s).' ) @@ -1430,3 +1464,253 @@ def rh_repo_module_manifest(module_sca_manifest_org, module_target_sat): rh_repo = module_target_sat.api.Repository(id=rh_repo_id).read() rh_repo.sync() return rh_repo + + +@pytest.mark.tier3 +def test_positive_incremental_update_apply_to_envs_cvs( + target_sat, + module_sca_manifest_org, + rhel8_contenthost, + module_product, +): + """With multiple environments and content views, register a host to one, + apply a CV filter to the content-view, and query available incremental update(s). + + Then, execute the available update with security errata, inspect the environment(s) and + content-view with the new incremental version. Check the errata and packages available on host. + + :id: ce8bd9ed-8fbc-40f2-8e58-e9b520fe94a3 + + :Setup: + 1. Security Errata synced on satellite server from custom repo. + 2. Multiple content-views promoted to multiple environments. + 3. Register a RHEL host to the content-view with activation key. + 4. Install outdated packages, some applicable to the erratum and some not. + + :Steps: + 1. Add an inclusive Erratum filter to the host content-view + 2. POST /api/hosts/bulk/available_incremental_updates + 3. POST /katello/api/content_view_versions/incremental_update + + :expectedresults: + 1. Incremental update is available to expected content-view in + expected environment(s), applicable to expected host. + 2. A new content-view incremental version is created and promoted, + the applicable errata are then available to the host. + 3. We can install packages and see updated applicable errata within the + incremental version of the content-view. + + """ + # any existing custom CVs in org, except Default CV + prior_cv_count = ( + len(target_sat.api.ContentView(organization=module_sca_manifest_org).search()) - 1 + ) + # Number to be Created: new LCE's for org, new CV's per LCE. + number_of_lces = 3 # Does not include 'Library' + number_of_cvs = 3 # Does not include 'Default Content View' + lce_library = _fetch_library_environment_for_org(target_sat, module_sca_manifest_org) + lce_list = [lce_library] + # custom repo with errata + custom_repo = target_sat.api.Repository( + product=module_product, content_type='yum', url=CUSTOM_REPO_URL + ).create() + custom_repo.sync() + + # create multiple linked environments + for n in range(number_of_lces): + new_lce = target_sat.api.LifecycleEnvironment( + organization=module_sca_manifest_org, + prior=lce_list[n], + ).create() + lce_list.append(new_lce) + assert len(lce_list) == number_of_lces + 1 + # collect default CV for org + default_cv = ( + target_sat.api.ContentView( + organization=module_sca_manifest_org, + name=DEFAULT_CV, + ) + .search()[0] + .read() + ) + cv_list = list([default_cv]) + # for each environment including 'Library' + for _lce in lce_list: + # create some new CVs with some content + for _i in range(number_of_cvs): + new_cv = target_sat.api.ContentView( + organization=module_sca_manifest_org, + repository=[custom_repo], + ).create() + # lces to be promoted to, omit newer than _lce in loop + env_ids = sorted([lce.id for lce in lce_list if lce.id <= _lce.id]) + # when the only lce to publish to is Library, pass None to default + if len(env_ids) == 1 and env_ids[0] == lce_library.id: + env_ids = None + # we may initially promote out of order, use force to bypass + new_cv = cv_publish_promote( + target_sat, + module_sca_manifest_org, + new_cv, + lce=env_ids, + force=True, + )['content-view'] + cv_list.append(new_cv) + + # total amount of CVs created matches expected and search results + assert len(cv_list) == 1 + (number_of_cvs * (number_of_lces + 1)) + assert prior_cv_count + len(cv_list) == len( + target_sat.api.ContentView(organization=module_sca_manifest_org).search() + ) + # one ak with newest CV and lce + host_lce = lce_list[-1].read() + host_cv = cv_list[-1].read() + ak = target_sat.api.ActivationKey( + organization=module_sca_manifest_org, + environment=host_lce, + content_view=host_cv, + ).create() + # content host, global registration + result = rhel8_contenthost.register( + org=module_sca_manifest_org, + activation_keys=ak.name, + target=target_sat, + loc=None, + ) + assert result.status == 0, f'Failed to register the host: {rhel8_contenthost.hostname}' + assert rhel8_contenthost.subscribed + rhel8_contenthost.execute(r'subscription-manager repos --enable \*') + # Installing all outdated packages + pkgs = ' '.join(FAKE_9_YUM_OUTDATED_PACKAGES) + assert rhel8_contenthost.execute(f'yum install -y {pkgs}').status == 0 + rhel8_contenthost.execute('subscription-manager repos') + # After installing packages, check available incremental updates + host = rhel8_contenthost.nailgun_host.read() + response = target_sat.api.Host().bulk_available_incremental_updates( + data={ + 'organization_id': module_sca_manifest_org.id, + 'included': {'ids': [host.id]}, + 'errata_ids': FAKE_9_YUM_SECURITY_ERRATUM, + }, + ) + # expecting no available updates before CV change + assert ( + response == [] + ), f'No incremental updates should currently be available to host: {rhel8_contenthost.hostname}.' + + # New Erratum CV filter created for host view + target_sat.api.ErratumContentViewFilter(content_view=host_cv, inclusion=True).create() + host_cv = target_sat.api.ContentView(id=host_cv.id).read() + lce_ids = sorted([lce.id for lce in lce_list]) + # publish version with filter and promote + host_cvv = cv_publish_promote( + target_sat, + module_sca_manifest_org, + host_cv, + lce_ids, + )['content-view-version'] + + # cv is not updated to host yet, applicable errata should be zero + rhel8_contenthost.execute('subscription-manager repos') + host_app_errata = rhel8_contenthost.applicable_errata_count + assert host_app_errata == 0 + # After adding filter to cv, check available incremental updates + host_app_packages = rhel8_contenthost.applicable_package_count + response = target_sat.api.Host().bulk_available_incremental_updates( + data={ + 'organization_id': module_sca_manifest_org.id, + 'included': {'ids': [host.id]}, + 'errata_ids': FAKE_9_YUM_SECURITY_ERRATUM, + }, + ) + assert ( + response + ), f'Expected one incremental update, but found none, for host: {rhel8_contenthost.hostname}.' + # find that only expected CV version has incremental update available + assert ( + len(response) == 1 + ), f'Incremental update should currently be available to only one host: {rhel8_contenthost.hostname}.' + next_version = float(response[0]['next_version']) + assert float(host_cvv.version) + 0.1 == next_version # example: 2.0 > 2.1 + assert response[0]['content_view_version']['id'] == host_cvv.id + assert response[0]['content_view_version']['content_view']['id'] == host_cv.id + + # Perform Incremental Update with host cv version + host_cvv = target_sat.api.ContentViewVersion(id=host_cvv.id).read() + # Apply incremental update adding the applicable security erratum + response = target_sat.api.ContentViewVersion().incremental_update( + data={ + 'content_view_version_environments': [ + { + 'content_view_version_id': host_cvv.id, + 'environment_ids': [host_lce.id], + } + ], + 'add_content': {'errata_ids': FAKE_9_YUM_SECURITY_ERRATUM}, + } + ) + assert response['result'] == 'success' + assert ( + response['action'] + == 'Incremental Update of 1 Content View Version(s) with 12 Package(s), and 3 Errata' + ) + + # only the hosts's CV was modified, new version made, check output details + assert len(response['output']['changed_content']) == 1 + created_version_id = response['output']['changed_content'][0]['content_view_version']['id'] + # host source CV version was changed to the new one + host_version_id = host.read().get_values()['content_facet_attributes'][ + 'content_view_version_id' + ] + assert host_version_id == created_version_id + # get newest version by highest id + version_ids = sorted([ver.id for ver in host_cv.read().version]) + assert created_version_id == version_ids[-1] + # latest sat and host version matches incremental one + host_version_number = float( + host.read().get_values()['content_facet_attributes']['content_view_version'] + ) + assert host_version_number == next_version + host_cvv = target_sat.api.ContentViewVersion(id=created_version_id).read() + assert float(host_cvv.version) == next_version + rhel8_contenthost.execute('subscription-manager repos') + # expected errata from FAKE_9 Security list added + added_errata = response['output']['changed_content'][0]['added_units']['erratum'] + assert set(added_errata) == set(FAKE_9_YUM_SECURITY_ERRATUM) + # applicable errata count increased by length of security ids list + assert rhel8_contenthost.applicable_errata_count == host_app_errata + len( + FAKE_9_YUM_SECURITY_ERRATUM + ) + # newly added errata from incremental version are now applicable to host + post_app_errata_ids = errata_id_set( + _fetch_available_errata_instances(target_sat, rhel8_contenthost) + ) + assert set(FAKE_9_YUM_SECURITY_ERRATUM).issubset(post_app_errata_ids) + # expected packages from the security erratum were added to host + added_packages = response['output']['changed_content'][0]['added_units']['rpm'] + assert 12 == len(added_packages) + # expected that not all of the added packages will be applicable + assert 8 == host_app_packages == rhel8_contenthost.applicable_package_count + # install all of the newly added packages, recalculate applicability + for pkg in added_packages: + assert rhel8_contenthost.run(f'yum install -y {pkg}').status == 0 + rhel8_contenthost.execute('subscription-manager repos') + # security errata should not be applicable after installing updated packages + post_app_errata_ids = errata_id_set( + _fetch_available_errata_instances(target_sat, rhel8_contenthost) + ) + assert set(FAKE_9_YUM_SECURITY_ERRATUM).isdisjoint(post_app_errata_ids) + assert rhel8_contenthost.applicable_errata_count == 0 + + # after applying the incremental update, check for any more available + response = target_sat.api.Host().bulk_available_incremental_updates( + data={ + 'organization_id': module_sca_manifest_org.id, + 'included': {'ids': [host.id]}, + 'errata_ids': FAKE_9_YUM_SECURITY_ERRATUM, + }, + ) + # expect no remaining updates, after applying the only one + assert ( + response == [] + ), f'No incremental updates should currently be available to host: {rhel8_contenthost.hostname}.' diff --git a/tests/foreman/api/test_provisioningtemplate.py b/tests/foreman/api/test_provisioningtemplate.py index b0128fca0a1..95f0b3883f0 100644 --- a/tests/foreman/api/test_provisioningtemplate.py +++ b/tests/foreman/api/test_provisioningtemplate.py @@ -472,7 +472,7 @@ def test_positive_template_use_graphical_installer( :expectedresults: Rendered template should contain value set as per use_graphical_installer host parameter for respective rhel hosts. - :BZ: 2106753 + :BZ: 2106753, 2193010 :customerscenario: true """ @@ -486,6 +486,8 @@ def test_positive_template_use_graphical_installer( render = host.read_template(data={'template_kind': 'provision'})['template'] assert 'skipx' in render assert 'text' in render + assert 'chvt 1' in render + # Using use_graphical_installer host param to override and use graphical mode to boot host.host_parameters_attributes = [ {'name': 'use_graphical_installer', 'value': 'true', 'parameter_type': 'boolean'} @@ -494,6 +496,7 @@ def test_positive_template_use_graphical_installer( render = host.read_template(data={'template_kind': 'provision'})['template'] assert 'graphical' in render assert 'skipx' not in render + assert 'chvt 6' in render @pytest.mark.rhel_ver_match('[8]') def test_positive_template_check_aap_snippet( diff --git a/tests/foreman/cli/test_realm.py b/tests/foreman/cli/test_realm.py index c05dc1291f8..e6480abd3a7 100644 --- a/tests/foreman/cli/test_realm.py +++ b/tests/foreman/cli/test_realm.py @@ -32,7 +32,7 @@ def test_negative_create_name_only(module_target_sat): @pytest.mark.tier1 -def test_negative_create_invalid_id(module_target_sat): +def test_negative_create_invalid_proxy_id(module_target_sat): """Create a realm with an invalid proxy ID :id: 916bd1fb-4649-469c-b511-b0b07301a990 diff --git a/tests/foreman/cli/test_report.py b/tests/foreman/cli/test_report.py index b46c3881a15..ea4f14371bb 100644 --- a/tests/foreman/cli/test_report.py +++ b/tests/foreman/cli/test_report.py @@ -14,6 +14,7 @@ import random import pytest +from wait_for import wait_for from robottelo.exceptions import CLIReturnCodeError @@ -82,11 +83,69 @@ def test_positive_install_configure_host( for client, puppet_proxy in zip(content_hosts, puppet_infra_host, strict=True): client.configure_puppet(proxy_hostname=puppet_proxy.hostname) report = session_puppet_enabled_sat.cli.ConfigReport.list( - {'search': f'host~{client.hostname},origin=Puppet'} + {'search': f'host~{client.hostname}'} ) assert len(report) + assert report[0]['origin'] == 'Puppet' facts = session_puppet_enabled_sat.cli.Fact.list({'search': f'host~{client.hostname}'}) assert len(facts) + assert [f for f in facts if f['fact'] == 'puppetmaster_fqdn'][0][ + 'value' + ] == puppet_proxy.hostname session_puppet_enabled_sat.cli.ConfigReport.delete({'id': report[0]['id']}) with pytest.raises(CLIReturnCodeError): session_puppet_enabled_sat.cli.ConfigReport.info({'id': report[0]['id']}) + + +@pytest.mark.e2e +@pytest.mark.rhel_ver_match('[^6]') +def test_positive_run_puppet_agent_generate_report_when_no_message( + session_puppet_enabled_sat, rhel_contenthost +): + """Verify that puppet-agent can be installed from the sat-client repo + and configured to report back to the Satellite, and contains the origin + + :id: 07777fbb-4f2e-4fab-ba5a-2b698f9b9f39 + + :setup: + 1. Satellite and Capsule with enabled puppet plugin. + 2. Blank RHEL content host + + :steps: + 1. Configure puppet on the content host. This creates sat-client repository, + installs puppet-agent, configures it, runs it to create the puppet cert, + signs in on the Satellite side and reruns it. + 2. Assert that Config report created in the Satellite for the content host, + and has Puppet origin + 3. Assert that Facts were reported for the content host. + + :expectedresults: + 1. Configuration passes without errors. + 2. Config report is created and contains Puppet origin + 3. Facts are acquired. + + :customerscenario: true + + :BZ: 2192939, 2257327, 2257314 + :parametrized: yes + """ + sat = session_puppet_enabled_sat + client = rhel_contenthost + client.configure_puppet(proxy_hostname=sat.hostname, run_puppet_agent=False) + # Run either 'puppet agent --detailed-exitcodes' or 'systemctl restart puppet' + # to generate Puppet config report for host without any messages + assert client.execute('/opt/puppetlabs/bin/puppet agent --detailed-exitcodes').status == 0 + wait_for( + lambda: sat.cli.ConfigReport.list({'search': f'host~{client.hostname}'}) != [], + timeout=300, + delay=30, + ) + report = sat.cli.ConfigReport.list({'search': f'host~{client.hostname}'}) + assert len(report) + assert report[0]['origin'] == 'Puppet' + facts = sat.cli.Fact.list({'search': f'host~{client.hostname}'}) + assert len(facts) + assert [f for f in facts if f['fact'] == 'puppetmaster_fqdn'][0]['value'] == sat.hostname + sat.cli.ConfigReport.delete({'id': report[0]['id']}) + with pytest.raises(CLIReturnCodeError): + sat.cli.ConfigReport.info({'id': report[0]['id']}) diff --git a/tests/foreman/sys/test_pulp3_filesystem.py b/tests/foreman/sys/test_pulp3_filesystem.py index af19af65f92..ac747ca5f10 100644 --- a/tests/foreman/sys/test_pulp3_filesystem.py +++ b/tests/foreman/sys/test_pulp3_filesystem.py @@ -27,15 +27,13 @@ def test_selinux_status(target_sat): :expectedresults: SELinux is enabled and there are no denials - :customerscenario: true - - :BZ: 2131031 + :BZ: 2263294 """ # check SELinux is enabled result = target_sat.execute('getenforce') assert 'Enforcing' in result.stdout # check there are no SELinux denials - if not is_open('BZ:2131031'): + if not is_open('BZ:2263294'): result = target_sat.execute('ausearch --input-logs -m avc -ts today --raw') assert result.status == 1, 'Some SELinux denials were found in journal.' diff --git a/tests/foreman/ui/test_host.py b/tests/foreman/ui/test_host.py index d59eb3bb403..f5a5f9680af 100644 --- a/tests/foreman/ui/test_host.py +++ b/tests/foreman/ui/test_host.py @@ -13,30 +13,25 @@ """ import copy import csv -from datetime import datetime import os -import re from airgun.exceptions import DisabledWidgetError, NoSuchElementException import pytest from wait_for import wait_for import yaml -from robottelo import constants from robottelo.config import settings from robottelo.constants import ( ANY_CONTEXT, DEFAULT_CV, DEFAULT_LOC, ENVIRONMENT, - FAKE_1_CUSTOM_PACKAGE, FAKE_7_CUSTOM_PACKAGE, FAKE_8_CUSTOM_PACKAGE, FAKE_8_CUSTOM_PACKAGE_NAME, OSCAP_PERIOD, OSCAP_WEEKDAY, PERMISSIONS, - REPO_TYPE, ) from robottelo.utils.datafactory import gen_string from robottelo.utils.issue_handlers import is_open @@ -984,587 +979,6 @@ def test_positive_validate_inherited_cv_lce_ansiblerole(session, target_sat, mod assert host.name in [host.name for host in matching_hosts] -@pytest.mark.tier2 -def test_positive_global_registration_form( - session, module_activation_key, module_org, smart_proxy_location, default_os, target_sat -): - """Host registration form produces a correct curl command for various inputs - - :id: f81c2ec4-85b1-4372-8e63-464ddbf70296 - - :customerscenario: true - - :expectedresults: The curl command contains all required parameters - """ - # rex and insights parameters are only specified in curl when differing from - # inerited parameters - result = ( - target_sat.api.CommonParameter() - .search(query={'search': 'name=host_registration_remote_execution'})[0] - .read() - ) - rex_value = not result.value - result = ( - target_sat.api.CommonParameter() - .search(query={'search': 'name=host_registration_insights'})[0] - .read() - ) - insights_value = not result.value - hostgroup = target_sat.api.HostGroup( - organization=[module_org], location=[smart_proxy_location] - ).create() - iface = 'eth0' - with session: - cmd = session.host.get_register_command( - { - 'advanced.setup_insights': 'Yes (override)' if insights_value else 'No (override)', - 'advanced.setup_rex': 'Yes (override)' if rex_value else 'No (override)', - 'general.insecure': True, - 'general.host_group': hostgroup.name, - 'general.operating_system': default_os.title, - 'general.activation_keys': module_activation_key.name, - 'advanced.update_packages': True, - 'advanced.rex_interface': iface, - } - ) - expected_pairs = [ - f'organization_id={module_org.id}', - f'activation_keys={module_activation_key.name}', - f'hostgroup_id={hostgroup.id}', - f'location_id={smart_proxy_location.id}', - f'operatingsystem_id={default_os.id}', - f'remote_execution_interface={iface}', - f'setup_insights={"true" if insights_value else "false"}', - f'setup_remote_execution={"true" if rex_value else "false"}', - f'{target_sat.hostname}', - 'insecure', - 'update_packages=true', - ] - for pair in expected_pairs: - assert pair in cmd - - -@pytest.mark.e2e -@pytest.mark.no_containers -@pytest.mark.tier3 -@pytest.mark.rhel_ver_match('[^6]') -def test_positive_global_registration_end_to_end( - session, - module_activation_key, - module_org, - smart_proxy_location, - default_os, - default_smart_proxy, - rhel_contenthost, - target_sat, -): - """Host registration form produces a correct registration command and host is - registered successfully with it, remote execution and insights are set up - - :id: a02658bf-097e-47a8-8472-5d9f649ba07a - - :customerscenario: true - - :BZ: 1993874 - - :expectedresults: Host is successfully registered, remote execution and insights - client work out of the box - - :parametrized: yes - """ - # make sure global parameters for rex and insights are set to true - insights_cp = ( - target_sat.api.CommonParameter() - .search(query={'search': 'name=host_registration_insights'})[0] - .read() - ) - rex_cp = ( - target_sat.api.CommonParameter() - .search(query={'search': 'name=host_registration_remote_execution'})[0] - .read() - ) - - if not insights_cp.value: - target_sat.api.CommonParameter(id=insights_cp.id, value=1).update(['value']) - if not rex_cp.value: - target_sat.api.CommonParameter(id=rex_cp.id, value=1).update(['value']) - - # rex interface - iface = 'eth0' - # fill in the global registration form - with session: - cmd = session.host.get_register_command( - { - 'general.operating_system': default_os.title, - 'general.activation_keys': module_activation_key.name, - 'advanced.update_packages': True, - 'advanced.rex_interface': iface, - 'general.insecure': True, - } - ) - expected_pairs = [ - f'organization_id={module_org.id}', - f'activation_keys={module_activation_key.name}', - f'location_id={smart_proxy_location.id}', - f'operatingsystem_id={default_os.id}', - f'{default_smart_proxy.name}', - 'insecure', - 'update_packages=true', - ] - for pair in expected_pairs: - assert pair in cmd - # rhel repo required for insights client installation, - # syncing it to the satellite would take too long - rhelver = rhel_contenthost.os_version.major - if rhelver > 7: - rhel_contenthost.create_custom_repos(**settings.repos[f'rhel{rhelver}_os']) - else: - rhel_contenthost.create_custom_repos( - **{f'rhel{rhelver}_os': settings.repos[f'rhel{rhelver}_os']} - ) - # make sure there will be package availabe for update - if rhel_contenthost.os_version.major == '6': - package = FAKE_1_CUSTOM_PACKAGE - repo_url = settings.repos.yum_1['url'] - else: - package = FAKE_7_CUSTOM_PACKAGE - repo_url = settings.repos.yum_3['url'] - rhel_contenthost.create_custom_repos(fake_yum=repo_url) - rhel_contenthost.execute(f"yum install -y {package}") - # run curl - result = rhel_contenthost.execute(cmd) - assert result.status == 0 - result = rhel_contenthost.execute('subscription-manager identity') - assert result.status == 0 - # Assert that a yum update was made this day ("Update" or "I, U" in history) - timezone_offset = rhel_contenthost.execute('date +"%:z"').stdout.strip() - tzinfo = datetime.strptime(timezone_offset, '%z').tzinfo - result = rhel_contenthost.execute('yum history | grep U') - assert result.status == 0 - assert datetime.now(tzinfo).strftime('%Y-%m-%d') in result.stdout - # Set "Connect to host using IP address" - target_sat.api.Parameter( - host=rhel_contenthost.hostname, - name='remote_execution_connect_by_ip', - parameter_type='boolean', - value='True', - ).create() - # run insights-client via REX - command = "insights-client --status" - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Script Default', - 'inputs': f'command={command}', - 'search-query': f"name ~ {rhel_contenthost.hostname}", - } - ) - # results provide all info but job invocation might not be finished yet - result = ( - target_sat.api.JobInvocation() - .search( - query={'search': f'id={invocation_command["id"]} and host={rhel_contenthost.hostname}'} - )[0] - .read() - ) - # make sure that task is finished - task_result = target_sat.wait_for_tasks( - search_query=(f'id = {result.task.id}'), search_rate=2, max_tries=60 - ) - assert task_result[0].result == 'success' - host = ( - target_sat.api.Host() - .search(query={'search': f'name={rhel_contenthost.hostname}'})[0] - .read() - ) - for interface in host.interface: - interface_result = target_sat.api.Interface(host=host.id).search( - query={'search': f'{interface.id}'} - )[0] - # more interfaces can be inside the host - if interface_result.identifier == iface: - assert interface_result.execution - - -@pytest.mark.tier2 -def test_global_registration_form_populate( - module_org, - session, - module_ak_with_cv, - module_lce, - module_promoted_cv, - default_architecture, - default_os, - target_sat, -): - """Host registration form should be populated automatically based on the host-group - - :id: b949e010-36b8-48b8-9907-36138342c72b - - :expectedresults: Some of the fields in the form should be populated based on host-group - e.g. activation key, operating system, life-cycle environment, host parameters for - remote-execution, insights setup. - - :steps: - 1. create and sync repository - 2. create the content view and activation-key - 3. create the host-group with activation key, operating system, host-parameters - 4. Open the global registration form and select the same host-group - 5. check host registration form should be populated automatically based on the host-group - - :BZ: 2056469, 1994654 - - :customerscenario: true - """ - hg_name = gen_string('alpha') - iface = gen_string('alpha') - group_params = {'name': 'host_packages', 'value': constants.FAKE_0_CUSTOM_PACKAGE} - target_sat.api.HostGroup( - name=hg_name, - organization=[module_org], - lifecycle_environment=module_lce, - architecture=default_architecture, - operatingsystem=default_os, - content_view=module_promoted_cv, - group_parameters_attributes=[group_params], - ).create() - new_org = target_sat.api.Organization().create() - new_ak = target_sat.api.ActivationKey(organization=new_org).create() - with session: - session.hostgroup.update( - hg_name, - { - 'activation_keys.activation_keys': module_ak_with_cv.name, - }, - ) - cmd = session.host.get_register_command( - { - 'general.host_group': hg_name, - 'advanced.rex_interface': iface, - 'general.insecure': True, - }, - full_read=True, - ) - assert hg_name in cmd['general']['host_group'] - assert module_ak_with_cv.name in cmd['general']['activation_key_helper'] - assert constants.FAKE_0_CUSTOM_PACKAGE in cmd['advanced']['install_packages_helper'] - - session.organization.select(org_name=new_org.name) - cmd = session.host.get_register_command( - { - 'general.organization': new_org.name, - 'general.operating_system': default_os.title, - 'general.insecure': True, - }, - full_read=True, - ) - assert new_org.name in cmd['general']['organization'] - assert new_ak.name in cmd['general']['activation_keys'] - - -@pytest.mark.tier2 -def test_global_registration_with_capsule_host( - session, - capsule_configured, - rhel8_contenthost, - module_org, - module_location, - module_product, - default_os, - module_lce_library, - target_sat, -): - """Host registration form produces a correct registration command and host is - registered successfully with selected capsule from form. - - :id: 6356c6d0-ee45-4ad7-8a0e-484d3490bc58 - - :expectedresults: Host is successfully registered with capsule host, - remote execution and insights client work out of the box - - :steps: - 1. create and sync repository - 2. create the content view and activation-key - 3. integrate capsule and sync content - 4. open the global registration form and select the same capsule - 5. check host is registered successfully with selected capsule - - :parametrized: yes - - :CaseAutomation: Automated - """ - client = rhel8_contenthost - repo = target_sat.api.Repository( - url=settings.repos.yum_1.url, - content_type=REPO_TYPE['yum'], - product=module_product, - ).create() - # Sync all repositories in the product - module_product.sync() - capsule = target_sat.api.Capsule(id=capsule_configured.nailgun_capsule.id).search( - query={'search': f'name={capsule_configured.hostname}'} - )[0] - module_org = target_sat.api.Organization(id=module_org.id).read() - module_org.smart_proxy.append(capsule) - module_location = target_sat.api.Location(id=module_location.id).read() - module_location.smart_proxy.append(capsule) - module_org.update(['smart_proxy']) - module_location.update(['smart_proxy']) - - # Associate the lifecycle environment with the capsule - capsule.content_add_lifecycle_environment(data={'environment_id': module_lce_library.id}) - result = capsule.content_lifecycle_environments() - # TODO result is not used, please add assert once you fix the test - - # Create a content view with the repository - cv = target_sat.api.ContentView(organization=module_org, repository=[repo]).create() - - # Publish new version of the content view - cv.publish() - cv = cv.read() - - assert len(cv.version) == 1 - - activation_key = target_sat.api.ActivationKey( - content_view=cv, environment=module_lce_library, organization=module_org - ).create() - - # Assert that a task to sync lifecycle environment to the capsule - # is started (or finished already) - sync_status = capsule.content_get_sync() - assert len(sync_status['active_sync_tasks']) >= 1 or sync_status['last_sync_time'] - - # Wait till capsule sync finishes - for task in sync_status['active_sync_tasks']: - target_sat.api.ForemanTask(id=task['id']).poll() - with session: - session.organization.select(org_name=module_org.name) - session.location.select(loc_name=module_location.name) - cmd = session.host.get_register_command( - { - 'general.organization': module_org.name, - 'general.location': module_location.name, - 'general.operating_system': default_os.title, - 'general.capsule': capsule_configured.hostname, - 'general.activation_keys': activation_key.name, - 'general.insecure': True, - } - ) - client.create_custom_repos(rhel7=settings.repos.rhel7_os) - # run curl - client.execute(cmd) - result = client.execute('subscription-manager identity') - assert result.status == 0 - assert module_lce_library.name in result.stdout - assert module_org.name in result.stdout - - -@pytest.mark.tier2 -@pytest.mark.usefixtures('enable_capsule_for_registration') -@pytest.mark.no_containers -def test_global_registration_with_gpg_repo_and_default_package( - session, module_activation_key, default_os, default_smart_proxy, rhel8_contenthost -): - """Host registration form produces a correct registration command and host is - registered successfully with gpg repo enabled and have default package - installed. - - :id: b5738b20-e281-4d0b-ac78-dcdc177b8c9f - - :expectedresults: Host is successfully registered, gpg repo is enabled - and default package is installed. - - :steps: - 1. create and sync repository - 2. create the content view and activation-key - 3. update the 'host_packages' parameter in organization with package name e.g. vim - 4. open the global registration form and update the gpg repo and key - 5. check host is registered successfully with installed same package - 6. check gpg repo is exist in registered host - - :parametrized: yes - """ - client = rhel8_contenthost - repo_name = 'foreman_register' - repo_url = settings.repos.gr_yum_repo.url - repo_gpg_url = settings.repos.gr_yum_repo.gpg_url - with session: - cmd = session.host.get_register_command( - { - 'general.operating_system': default_os.title, - 'general.capsule': default_smart_proxy.name, - 'general.activation_keys': module_activation_key.name, - 'general.insecure': True, - 'advanced.force': True, - 'advanced.install_packages': 'mlocate vim', - 'advanced.repository': repo_url, - 'advanced.repository_gpg_key_url': repo_gpg_url, - } - ) - - # rhel repo required for insights client installation, - # syncing it to the satellite would take too long - rhelver = client.os_version.major - if rhelver > 7: - repos = {f'rhel{rhelver}_os': settings.repos[f'rhel{rhelver}_os']['baseos']} - else: - repos = { - 'rhel7_os': settings.repos['rhel7_os'], - 'rhel7_extras': settings.repos['rhel7_extras'], - } - client.create_custom_repos(**repos) - # run curl - result = client.execute(cmd) - assert result.status == 0 - result = client.execute('yum list installed | grep mlocate') - assert result.status == 0 - assert 'mlocate' in result.stdout - result = client.execute(f'yum -v repolist {repo_name}') - assert result.status == 0 - assert repo_url in result.stdout - - -@pytest.mark.tier2 -@pytest.mark.rhel_ver_match('[^6].*') -def test_global_registration_upgrade_subscription_manager( - session, module_activation_key, module_os, rhel_contenthost -): - """Host registration form produces a correct registration command and - subscription-manager can be updated from a custom repository before - registration is completed. - - :id: b7a44f32-90b2-4fd6-b65b-5a3d2a5c5deb - - :customerscenario: true - - :expectedresults: Host is successfully registered, repo is enabled - on advanced tab and subscription-manager is updated. - - :steps: - 1. Create activation-key - 2. Open the global registration form, add repo and activation key - 3. Add 'subscription-manager' to install packages field - 4. Check subscription-manager was installed from repo_name - - :parametrized: yes - - :BZ: 1923320 - """ - client = rhel_contenthost - repo_name = 'foreman_register' - rhel_ver = rhel_contenthost.os_version.major - repo_url = settings.repos.get(f'rhel{rhel_ver}_os') - if isinstance(repo_url, dict): - repo_url = repo_url['baseos'] - # Ensure subs-man is installed from repo_name by removing existing package. - result = client.execute('rpm --erase --nodeps subscription-manager') - assert result.status == 0 - with session: - cmd = session.host.get_register_command( - { - 'general.operating_system': module_os.title, - 'general.activation_keys': module_activation_key.name, - 'general.insecure': True, - 'advanced.force': True, - 'advanced.install_packages': 'subscription-manager', - 'advanced.repository': repo_url, - } - ) - - # run curl - result = client.execute(cmd) - assert result.status == 0 - result = client.execute('yum repolist') - assert repo_name in result.stdout - assert result.status == 0 - - -@pytest.mark.tier3 -@pytest.mark.usefixtures('enable_capsule_for_registration') -@pytest.mark.rhel_ver_match('[^6].*') -def test_global_re_registration_host_with_force_ignore_error_options( - session, module_activation_key, default_os, default_smart_proxy, rhel_contenthost -): - """If the ignore_error and force checkbox is checked then registered host can - get re-registered without any error. - - :id: 8f0ecc13-5d18-4adb-acf5-3f3276dccbb7 - - :expectedresults: Verify the force and ignore checkbox options - - :steps: - 1. create and sync repository - 2. create the content view and activation-key - 3. open the global registration form and select --force and --Ignore Errors option - 4. registered the host with generated curl command - 5. re-register the same host again and check it is getting registered - - :parametrized: yes - """ - client = rhel_contenthost - with session: - cmd = session.host.get_register_command( - { - 'general.operating_system': default_os.title, - 'general.capsule': default_smart_proxy.name, - 'general.activation_keys': module_activation_key.name, - 'general.insecure': True, - 'advanced.force': True, - 'advanced.ignore_error': True, - } - ) - result = client.execute(cmd) - assert result.status == 0 - result = client.execute('subscription-manager identity') - assert result.status == 0 - # rerun the register command - result = client.execute(cmd) - assert result.status == 0 - result = client.execute('subscription-manager identity') - assert result.status == 0 - - -@pytest.mark.tier2 -@pytest.mark.usefixtures('enable_capsule_for_registration') -def test_global_registration_token_restriction( - session, module_activation_key, rhel8_contenthost, default_os, default_smart_proxy, target_sat -): - """Global registration token should be only used for registration call, it - should be restricted for any other api calls. - - :id: 4528b5c6-0a6d-40cd-857a-68b76db2179b - - :expectedresults: global registration token should be restricted for any api calls - other than the registration - - :steps: - 1. open the global registration form and generate the curl token - 2. use that curl token to execute other api calls e.g. GET /hosts, /users - - :parametrized: yes - """ - client = rhel8_contenthost - with session: - cmd = session.host.get_register_command( - { - 'general.operating_system': default_os.title, - 'general.capsule': default_smart_proxy.name, - 'general.activation_keys': module_activation_key.name, - 'general.insecure': True, - } - ) - - pattern = re.compile("Authorization.*(?=')") - auth_header = re.search(pattern, cmd).group() - - # build curl - curl_users = f'curl -X GET -k -H {auth_header} -i {target_sat.url}/api/users/' - curl_hosts = f'curl -X GET -k -H {auth_header} -i {target_sat.url}/api/hosts/' - for curl_cmd in (curl_users, curl_hosts): - result = client.execute(curl_cmd) - assert result.status == 0 - assert 'Unable to authenticate user' in result.stdout - - @pytest.mark.tier4 @pytest.mark.upgrade def test_positive_bulk_delete_host(session, smart_proxy_location, target_sat, function_org): @@ -2314,59 +1728,6 @@ def test_positive_tracer_enable_reload(tracer_install_host, target_sat): assert tracer['title'] == "No applications to restart" -def test_positive_host_registration_with_non_admin_user( - test_name, - module_sca_manifest_org, - module_location, - target_sat, - rhel8_contenthost, - module_activation_key, -): - """Register hosts from a non-admin user with only register_hosts, edit_hosts - and view_organization permissions - - :id: 35458bbc-4556-41b9-ba26-ae0b15179731 - - :expectedresults: User with register hosts permission able to register hosts. - """ - user_password = gen_string('alpha') - org = module_sca_manifest_org - role = target_sat.api.Role(organization=[org]).create() - - user_permissions = { - 'Organization': ['view_organizations'], - 'Host': ['view_hosts'], - } - target_sat.api_factory.create_role_permissions(role, user_permissions) - user = target_sat.api.User( - role=[role], - admin=False, - password=user_password, - organization=[org], - location=[module_location], - default_organization=org, - default_location=module_location, - ).create() - role = target_sat.cli.Role.info({'name': 'Register hosts'}) - target_sat.cli.User.add_role({'id': user.id, 'role-id': role['id']}) - - with target_sat.ui_session(test_name, user=user.login, password=user_password) as session: - - cmd = session.host_new.get_register_command( - { - 'general.insecure': True, - 'general.activation_keys': module_activation_key.name, - } - ) - - result = rhel8_contenthost.execute(cmd) - assert result.status == 0, f'Failed to register host: {result.stderr}' - - # Verify server.hostname and server.port from subscription-manager config - assert target_sat.hostname == rhel8_contenthost.subscription_config['server']['hostname'] - assert constants.CLIENT_PORT == rhel8_contenthost.subscription_config['server']['port'] - - @pytest.mark.tier2 def test_all_hosts_delete(target_sat, function_org, function_location, new_host_ui): """Create a host and delete it through All Hosts UI diff --git a/tests/foreman/ui/test_registration.py b/tests/foreman/ui/test_registration.py index 7796d2a5961..d9b8ee288b8 100644 --- a/tests/foreman/ui/test_registration.py +++ b/tests/foreman/ui/test_registration.py @@ -10,10 +10,17 @@ :Team: Rocket """ +from datetime import datetime +import re + from airgun.exceptions import DisabledWidgetError +from airgun.session import Session +from fauxfactory import gen_string import pytest -from robottelo.utils.datafactory import gen_string +from robottelo import constants +from robottelo.config import settings +from robottelo.constants import FAKE_1_CUSTOM_PACKAGE, FAKE_7_CUSTOM_PACKAGE, REPO_TYPE pytestmark = pytest.mark.tier1 @@ -111,3 +118,579 @@ def test_negative_global_registration_without_ak( with pytest.raises(DisabledWidgetError) as context: session.host.get_register_command() assert 'Generate registration command button is disabled' in str(context.value) + + +@pytest.mark.e2e +@pytest.mark.no_containers +@pytest.mark.tier3 +@pytest.mark.rhel_ver_match('[^6]') +def test_positive_global_registration_end_to_end( + session, + module_activation_key, + module_org, + smart_proxy_location, + default_os, + default_smart_proxy, + rhel_contenthost, + target_sat, +): + """Host registration form produces a correct registration command and host is + registered successfully with it, remote execution and insights are set up + + :id: a02658bf-097e-47a8-8472-5d9f649ba07a + + :customerscenario: true + + :BZ: 1993874 + + :expectedresults: Host is successfully registered, remote execution and insights + client work out of the box + + :parametrized: yes + """ + # make sure global parameters for rex and insights are set to true + insights_cp = ( + target_sat.api.CommonParameter() + .search(query={'search': 'name=host_registration_insights'})[0] + .read() + ) + rex_cp = ( + target_sat.api.CommonParameter() + .search(query={'search': 'name=host_registration_remote_execution'})[0] + .read() + ) + + if not insights_cp.value: + target_sat.api.CommonParameter(id=insights_cp.id, value=1).update(['value']) + if not rex_cp.value: + target_sat.api.CommonParameter(id=rex_cp.id, value=1).update(['value']) + + # rex interface + iface = 'eth0' + # fill in the global registration form + with session: + cmd = session.host.get_register_command( + { + 'general.operating_system': default_os.title, + 'general.activation_keys': module_activation_key.name, + 'advanced.update_packages': True, + 'advanced.rex_interface': iface, + 'general.insecure': True, + } + ) + expected_pairs = [ + f'organization_id={module_org.id}', + f'activation_keys={module_activation_key.name}', + f'location_id={smart_proxy_location.id}', + f'operatingsystem_id={default_os.id}', + f'{default_smart_proxy.name}', + 'insecure', + 'update_packages=true', + ] + for pair in expected_pairs: + assert pair in cmd + # rhel repo required for insights client installation, + # syncing it to the satellite would take too long + rhelver = rhel_contenthost.os_version.major + if rhelver > 7: + rhel_contenthost.create_custom_repos(**settings.repos[f'rhel{rhelver}_os']) + else: + rhel_contenthost.create_custom_repos( + **{f'rhel{rhelver}_os': settings.repos[f'rhel{rhelver}_os']} + ) + # make sure there will be package availabe for update + if rhel_contenthost.os_version.major == '6': + package = FAKE_1_CUSTOM_PACKAGE + repo_url = settings.repos.yum_1['url'] + else: + package = FAKE_7_CUSTOM_PACKAGE + repo_url = settings.repos.yum_3['url'] + rhel_contenthost.create_custom_repos(fake_yum=repo_url) + rhel_contenthost.execute(f"yum install -y {package}") + # run curl + result = rhel_contenthost.execute(cmd) + assert result.status == 0 + result = rhel_contenthost.execute('subscription-manager identity') + assert result.status == 0 + # Assert that a yum update was made this day ("Update" or "I, U" in history) + timezone_offset = rhel_contenthost.execute('date +"%:z"').stdout.strip() + tzinfo = datetime.strptime(timezone_offset, '%z').tzinfo + result = rhel_contenthost.execute('yum history | grep U') + assert result.status == 0 + assert datetime.now(tzinfo).strftime('%Y-%m-%d') in result.stdout + # Set "Connect to host using IP address" + target_sat.api.Parameter( + host=rhel_contenthost.hostname, + name='remote_execution_connect_by_ip', + parameter_type='boolean', + value='True', + ).create() + # run insights-client via REX + command = "insights-client --status" + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Script Default', + 'inputs': f'command={command}', + 'search-query': f"name ~ {rhel_contenthost.hostname}", + } + ) + # results provide all info but job invocation might not be finished yet + result = ( + target_sat.api.JobInvocation() + .search( + query={'search': f'id={invocation_command["id"]} and host={rhel_contenthost.hostname}'} + )[0] + .read() + ) + # make sure that task is finished + task_result = target_sat.wait_for_tasks( + search_query=(f'id = {result.task.id}'), search_rate=2, max_tries=60 + ) + assert task_result[0].result == 'success' + host = ( + target_sat.api.Host() + .search(query={'search': f'name={rhel_contenthost.hostname}'})[0] + .read() + ) + for interface in host.interface: + interface_result = target_sat.api.Interface(host=host.id).search( + query={'search': f'{interface.id}'} + )[0] + # more interfaces can be inside the host + if interface_result.identifier == iface: + assert interface_result.execution + + +@pytest.mark.tier2 +def test_global_registration_form_populate( + module_org, + session, + module_ak_with_cv, + module_lce, + module_promoted_cv, + default_architecture, + default_os, + target_sat, +): + """Host registration form should be populated automatically based on the host-group + + :id: b949e010-36b8-48b8-9907-36138342c72b + + :expectedresults: Some of the fields in the form should be populated based on host-group + e.g. activation key, operating system, life-cycle environment, host parameters for + remote-execution, insights setup. + + :steps: + 1. create and sync repository + 2. create the content view and activation-key + 3. create the host-group with activation key, operating system, host-parameters + 4. Open the global registration form and select the same host-group + 5. check host registration form should be populated automatically based on the host-group + + :BZ: 2056469, 1994654 + + :customerscenario: true + """ + hg_name = gen_string('alpha') + iface = gen_string('alpha') + group_params = {'name': 'host_packages', 'value': constants.FAKE_0_CUSTOM_PACKAGE} + target_sat.api.HostGroup( + name=hg_name, + organization=[module_org], + lifecycle_environment=module_lce, + architecture=default_architecture, + operatingsystem=default_os, + content_view=module_promoted_cv, + group_parameters_attributes=[group_params], + ).create() + new_org = target_sat.api.Organization().create() + new_ak = target_sat.api.ActivationKey(organization=new_org).create() + with session: + session.hostgroup.update( + hg_name, + { + 'activation_keys.activation_keys': module_ak_with_cv.name, + }, + ) + cmd = session.host.get_register_command( + { + 'general.host_group': hg_name, + 'advanced.rex_interface': iface, + 'general.insecure': True, + }, + full_read=True, + ) + assert hg_name in cmd['general']['host_group'] + assert module_ak_with_cv.name in cmd['general']['activation_key_helper'] + assert constants.FAKE_0_CUSTOM_PACKAGE in cmd['advanced']['install_packages_helper'] + + session.organization.select(org_name=new_org.name) + cmd = session.host.get_register_command( + { + 'general.organization': new_org.name, + 'general.operating_system': default_os.title, + 'general.insecure': True, + }, + full_read=True, + ) + assert new_org.name in cmd['general']['organization'] + assert new_ak.name in cmd['general']['activation_keys'] + + +@pytest.mark.tier2 +@pytest.mark.usefixtures('enable_capsule_for_registration') +@pytest.mark.no_containers +def test_global_registration_with_gpg_repo_and_default_package( + session, module_activation_key, default_os, default_smart_proxy, rhel8_contenthost +): + """Host registration form produces a correct registration command and host is + registered successfully with gpg repo enabled and have default package + installed. + + :id: b5738b20-e281-4d0b-ac78-dcdc177b8c9f + + :expectedresults: Host is successfully registered, gpg repo is enabled + and default package is installed. + + :steps: + 1. create and sync repository + 2. create the content view and activation-key + 3. update the 'host_packages' parameter in organization with package name e.g. vim + 4. open the global registration form and update the gpg repo and key + 5. check host is registered successfully with installed same package + 6. check gpg repo is exist in registered host + + :parametrized: yes + """ + client = rhel8_contenthost + repo_name = 'foreman_register' + repo_url = settings.repos.gr_yum_repo.url + repo_gpg_url = settings.repos.gr_yum_repo.gpg_url + with session: + cmd = session.host.get_register_command( + { + 'general.operating_system': default_os.title, + 'general.capsule': default_smart_proxy.name, + 'general.activation_keys': module_activation_key.name, + 'general.insecure': True, + 'advanced.force': True, + 'advanced.install_packages': 'mlocate vim', + 'advanced.repository': repo_url, + 'advanced.repository_gpg_key_url': repo_gpg_url, + } + ) + + # rhel repo required for insights client installation, + # syncing it to the satellite would take too long + rhelver = client.os_version.major + if rhelver > 7: + repos = {f'rhel{rhelver}_os': settings.repos[f'rhel{rhelver}_os']['baseos']} + else: + repos = { + 'rhel7_os': settings.repos['rhel7_os'], + 'rhel7_extras': settings.repos['rhel7_extras'], + } + client.create_custom_repos(**repos) + # run curl + result = client.execute(cmd) + assert result.status == 0 + result = client.execute('yum list installed | grep mlocate') + assert result.status == 0 + assert 'mlocate' in result.stdout + result = client.execute(f'yum -v repolist {repo_name}') + assert result.status == 0 + assert repo_url in result.stdout + + +@pytest.mark.tier3 +@pytest.mark.usefixtures('enable_capsule_for_registration') +def test_global_re_registration_host_with_force_ignore_error_options( + session, module_activation_key, default_os, default_smart_proxy, rhel7_contenthost +): + """If the ignore_error and force checkbox is checked then registered host can + get re-registered without any error. + + :id: 8f0ecc13-5d18-4adb-acf5-3f3276dccbb7 + + :expectedresults: Verify the force and ignore checkbox options + + :steps: + 1. create and sync repository + 2. create the content view and activation-key + 3. open the global registration form and select --force and --Ignore Errors option + 4. registered the host with generated curl command + 5. re-register the same host again and check it is getting registered + + :parametrized: yes + """ + client = rhel7_contenthost + with session: + cmd = session.host.get_register_command( + { + 'general.operating_system': default_os.title, + 'general.capsule': default_smart_proxy.name, + 'general.activation_keys': module_activation_key.name, + 'general.insecure': True, + 'advanced.force': True, + 'advanced.ignore_error': True, + } + ) + client.execute(cmd) + result = client.execute('subscription-manager identity') + assert result.status == 0 + # rerun the register command + client.execute(cmd) + result = client.execute('subscription-manager identity') + assert result.status == 0 + + +@pytest.mark.tier2 +@pytest.mark.usefixtures('enable_capsule_for_registration') +def test_global_registration_token_restriction( + session, module_activation_key, rhel8_contenthost, default_os, default_smart_proxy, target_sat +): + """Global registration token should be only used for registration call, it + should be restricted for any other api calls. + + :id: 4528b5c6-0a6d-40cd-857a-68b76db2179b + + :expectedresults: global registration token should be restricted for any api calls + other than the registration + + :steps: + 1. open the global registration form and generate the curl token + 2. use that curl token to execute other api calls e.g. GET /hosts, /users + + :parametrized: yes + """ + client = rhel8_contenthost + with session: + cmd = session.host.get_register_command( + { + 'general.operating_system': default_os.title, + 'general.capsule': default_smart_proxy.name, + 'general.activation_keys': module_activation_key.name, + 'general.insecure': True, + } + ) + + pattern = re.compile("Authorization.*(?=')") + auth_header = re.search(pattern, cmd).group() + + # build curl + curl_users = f'curl -X GET -k -H {auth_header} -i {target_sat.url}/api/users/' + curl_hosts = f'curl -X GET -k -H {auth_header} -i {target_sat.url}/api/hosts/' + for curl_cmd in (curl_users, curl_hosts): + result = client.execute(curl_cmd) + assert result.status == 0 + assert 'Unable to authenticate user' in result.stdout + + +def test_positive_host_registration_with_non_admin_user( + test_name, + module_sca_manifest_org, + module_location, + target_sat, + rhel8_contenthost, + module_activation_key, +): + """Register hosts from a non-admin user with only register_hosts, edit_hosts + and view_organization permissions + + :id: 35458bbc-4556-41b9-ba26-ae0b15179731 + + :expectedresults: User with register hosts permission able to register hosts. + """ + user_password = gen_string('alpha') + org = module_sca_manifest_org + role = target_sat.api.Role(organization=[org]).create() + + user_permissions = { + 'Organization': ['view_organizations'], + 'Host': ['view_hosts'], + } + target_sat.api_factory.create_role_permissions(role, user_permissions) + user = target_sat.api.User( + role=[role], + admin=False, + password=user_password, + organization=[org], + location=[module_location], + default_organization=org, + default_location=module_location, + ).create() + role = target_sat.cli.Role.info({'name': 'Register hosts'}) + target_sat.cli.User.add_role({'id': user.id, 'role-id': role['id']}) + + with Session(test_name, user=user.login, password=user_password) as session: + + cmd = session.host_new.get_register_command( + { + 'general.insecure': True, + 'general.activation_keys': module_activation_key.name, + } + ) + + result = rhel8_contenthost.execute(cmd) + assert result.status == 0, f'Failed to register host: {result.stderr}' + + # Verify server.hostname and server.port from subscription-manager config + assert target_sat.hostname == rhel8_contenthost.subscription_config['server']['hostname'] + assert constants.CLIENT_PORT == rhel8_contenthost.subscription_config['server']['port'] + + +@pytest.mark.tier2 +def test_positive_global_registration_form( + session, module_activation_key, module_org, smart_proxy_location, default_os, target_sat +): + """Host registration form produces a correct curl command for various inputs + + :id: f81c2ec4-85b1-4372-8e63-464ddbf70296 + + :customerscenario: true + + :expectedresults: The curl command contains all required parameters + """ + # rex and insights parameters are only specified in curl when differing from + # inerited parameters + result = ( + target_sat.api.CommonParameter() + .search(query={'search': 'name=host_registration_remote_execution'})[0] + .read() + ) + rex_value = not result.value + result = ( + target_sat.api.CommonParameter() + .search(query={'search': 'name=host_registration_insights'})[0] + .read() + ) + insights_value = not result.value + hostgroup = target_sat.api.HostGroup( + organization=[module_org], location=[smart_proxy_location] + ).create() + iface = 'eth0' + with session: + cmd = session.host.get_register_command( + { + 'advanced.setup_insights': 'Yes (override)' if insights_value else 'No (override)', + 'advanced.setup_rex': 'Yes (override)' if rex_value else 'No (override)', + 'general.insecure': True, + 'general.host_group': hostgroup.name, + 'general.operating_system': default_os.title, + 'general.activation_keys': module_activation_key.name, + 'advanced.update_packages': True, + 'advanced.rex_interface': iface, + } + ) + expected_pairs = [ + f'organization_id={module_org.id}', + f'activation_keys={module_activation_key.name}', + f'hostgroup_id={hostgroup.id}', + f'location_id={smart_proxy_location.id}', + f'operatingsystem_id={default_os.id}', + f'remote_execution_interface={iface}', + f'setup_insights={"true" if insights_value else "false"}', + f'setup_remote_execution={"true" if rex_value else "false"}', + f'{target_sat.hostname}', + 'insecure', + 'update_packages=true', + ] + for pair in expected_pairs: + assert pair in cmd + + +@pytest.mark.tier2 +def test_global_registration_with_capsule_host( + session, + capsule_configured, + rhel8_contenthost, + module_org, + module_location, + module_product, + default_os, + module_lce_library, + target_sat, +): + """Host registration form produces a correct registration command and host is + registered successfully with selected capsule from form. + + :id: 6356c6d0-ee45-4ad7-8a0e-484d3490bc58 + + :expectedresults: Host is successfully registered with capsule host, + remote execution and insights client work out of the box + + :steps: + 1. create and sync repository + 2. create the content view and activation-key + 3. integrate capsule and sync content + 4. open the global registration form and select the same capsule + 5. check host is registered successfully with selected capsule + + :parametrized: yes + + :CaseAutomation: Automated + """ + client = rhel8_contenthost + repo = target_sat.api.Repository( + url=settings.repos.yum_1.url, + content_type=REPO_TYPE['yum'], + product=module_product, + ).create() + # Sync all repositories in the product + module_product.sync() + capsule = target_sat.api.Capsule(id=capsule_configured.nailgun_capsule.id).search( + query={'search': f'name={capsule_configured.hostname}'} + )[0] + module_org = target_sat.api.Organization(id=module_org.id).read() + module_org.smart_proxy.append(capsule) + module_location = target_sat.api.Location(id=module_location.id).read() + module_location.smart_proxy.append(capsule) + module_org.update(['smart_proxy']) + module_location.update(['smart_proxy']) + + # Associate the lifecycle environment with the capsule + capsule.content_add_lifecycle_environment(data={'environment_id': module_lce_library.id}) + result = capsule.content_lifecycle_environments() + # TODO result is not used, please add assert once you fix the test + + # Create a content view with the repository + cv = target_sat.api.ContentView(organization=module_org, repository=[repo]).create() + + # Publish new version of the content view + cv.publish() + cv = cv.read() + + assert len(cv.version) == 1 + + activation_key = target_sat.api.ActivationKey( + content_view=cv, environment=module_lce_library, organization=module_org + ).create() + + # Assert that a task to sync lifecycle environment to the capsule + # is started (or finished already) + sync_status = capsule.content_get_sync() + assert len(sync_status['active_sync_tasks']) >= 1 or sync_status['last_sync_time'] + + # Wait till capsule sync finishes + for task in sync_status['active_sync_tasks']: + target_sat.api.ForemanTask(id=task['id']).poll() + with session: + session.organization.select(org_name=module_org.name) + session.location.select(loc_name=module_location.name) + cmd = session.host.get_register_command( + { + 'general.organization': module_org.name, + 'general.location': module_location.name, + 'general.operating_system': default_os.title, + 'general.capsule': capsule_configured.hostname, + 'general.activation_keys': activation_key.name, + 'general.insecure': True, + } + ) + client.create_custom_repos(rhel7=settings.repos.rhel7_os) + # run curl + client.execute(cmd) + result = client.execute('subscription-manager identity') + assert result.status == 0 + assert module_lce_library.name in result.stdout + assert module_org.name in result.stdout