diff --git a/.github/workflows/auto_branching.yml b/.github/workflows/auto_branching.yml new file mode 100644 index 00000000000..6afb0cfa164 --- /dev/null +++ b/.github/workflows/auto_branching.yml @@ -0,0 +1,317 @@ +### The auto-branching workflow triggered through a dispatch request from the CI +name: auto-branching + +# Run on workflow dispatch from CI +on: + workflow_dispatch: + inputs: + target_branch: + type: string + description: branch to be created from the master + stream_version: + type: string + description: new stream version of satellite + +jobs: + check-group-membership: + runs-on: ubuntu-latest + outputs: + member: ${{steps.check_membership.outputs.member}} + + steps: + - name: Check if the user is a member of repository-admins group + id: check_membership + run: | + # Use GitHub API to check if the user triggering the workflow is a member of satellite-admin group + MEMBER=$(curl -s -H "Authorization: token ${{ secrets._REPO_ADMIN_TOKEN }}" \ + "https://api.github.com/orgs/satelliteQE/teams/repository-admins/memberships/${{ github.actor }}") + if [[ $(echo "$MEMBER" | jq -r '.state') == "active" ]]; then + echo "User is a member of satellite-admin group." + echo "member=true" >> $GITHUB_OUTPUT + else + echo "User is not a member of satellite-admin group." + echo "member=false" >> $GITHUB_OUTPUT + exit 1 + fi + + auto-branching-new-downstream-release: + name: ${{ github.event.inputs.target_branch }} - raise PR with changes + runs-on: ubuntu-latest + needs: check-group-membership + if: ${{ needs.check-group-membership.outputs.member == 'true' }} + + steps: + - uses: actions/checkout@v4 + + - name: Create the ${{ github.event.inputs.target_branch }} branch + id: create-branch + uses: peterjgrainger/action-create-branch@v3.0.0 + env: + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + with: + branch: ${{ github.event.inputs.target_branch }} + + - name: Create label for the ${{ github.event.inputs.target_branch }} branch + id: create-label + run: | + curl -X POST \ + -H "Authorization: token ${{ secrets._REPO_ADMIN_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/labels \ + -d "{\"name\":\"${{ github.event.inputs.target_branch }}\",\"color\":\"fbca04\"}" + + - name: Switch to ${{ github.event.inputs.target_branch }} branch + run: git checkout -b "${{ github.event.inputs.target_branch }}" + + - name: Checkout from ${{ github.event.inputs.target_branch }} branch for auto-branching changes + id: checkout-to-auto-branch + run: | + branch_name="auto-branching-${{ github.event.inputs.target_branch }}-$(date '+%s')" + git checkout -b "$branch_name" + echo "branch_name=$branch_name" >> $GITHUB_OUTPUT + + - name: Update target branch label in dependabot yml file + id: update-dependabot + run: | + # Read the dependabot.yml file + FILE_PATH="./.github/dependabot.yml" + TARGET_BRANCH="${{ github.event.inputs.target_branch }}" + # Append the target branch label to the labels node + awk -v target="'$TARGET_BRANCH'" '/^ *labels:/ {$0 = $0 "\n - " target} 1' "$FILE_PATH" > temp.yml && mv temp.yml "$FILE_PATH" + + - name: Update repository URLs in requirements.txt + id: update-repo-urls + run: | + # Define the file path + FILE_PATH="./requirements.txt" + # Define the replacement strings + replacements=( + "airgun @ git+https://github.com/SatelliteQE/airgun.git@master#egg=airgun|airgun @ git+https://github.com/SatelliteQE/airgun.git@${{ github.event.inputs.target_branch }}#egg=airgun" + "nailgun @ git+https://github.com/SatelliteQE/nailgun.git@master#egg=nailgun|nailgun @ git+https://github.com/SatelliteQE/nailgun.git@${{ github.event.inputs.target_branch }}#egg=nailgun" + ) + # Create a temporary file + TEMP_FILE=$(mktemp) + # Perform replacements using a for loop + for replacement in "${replacements[@]}"; do + old_url=$(echo "$replacement" | cut -d'|' -f1) + new_url=$(echo "$replacement" | cut -d'|' -f2) + sed "s|${old_url}|${new_url}|g" "$FILE_PATH" > "$TEMP_FILE" && mv "$TEMP_FILE" "$FILE_PATH" + done + + - name: Remove the dispatch release GHA + id: remove-dispatch-release-gha + run: | + rm -rf ./.github/workflows/dispatch_release.yml + rm -rf ./.github/workflows/auto_branching.yml + + - name: Remove lines with @pytest.mark.stream + id: remove-mark-stream + run: | + # Loop through files in the folder + grep -rl "tests/foreman" -e '@pytest\.mark\.stream' | while IFS= read -r file; do + awk '!/@pytest\.mark\.stream/' "$file" > temp && mv temp "$file" + done + + - name: Update version in setup.py + run: sed -i "s/version=['\"][0-9.]*['\"]\+/version='${{ github.event.inputs.target_branch }}'/" setup.py + + - name: Update the Constants in __init__.py file + run: | + old_url="https://raw.githubusercontent.com/SatelliteQE/robottelo/master/tests/foreman/data/uri.sh" + new_url="https://raw.githubusercontent.com/SatelliteQE/robottelo/${{ github.event.inputs.target_branch }}/tests/foreman/data/uri.sh" + FILE_PATH="./robottelo/constants/__init__.py" + awk '/SAT_NON_GA_VERSIONS =/ { sub(/\[[^,]*, /, "[", $0) } 1' "$FILE_PATH" > temp && mv temp "$FILE_PATH" + sed -i.bak "s|${old_url}|${new_url}|" "$FILE_PATH" + rm "$FILE_PATH.bak" + + - name: git status + run: git status + + - name: git diff + run: git diff + + - name: Commit changes + run: | + git config --local user.email Satellite-QE.satqe.com && git config --local user.name Satellite-QE + git add setup.py ./tests/foreman ./robottelo/* ./requirements.txt ./.github/* + git commit -m "Changes for ${{ github.event.inputs.target_branch }} new branch" + git push origin ${{steps.checkout-to-auto-branch.outputs.branch_name}} + + - name: Create pull request + id: create_pr + run: | + title="[${{ github.event.inputs.target_branch }}]: Changes for ${{ github.event.inputs.target_branch }} new branch" + body=" + ### Problem Statement + New ${{ github.event.inputs.target_branch }} branch + ### Solution + - Dependabot labels are updated for new branch + - Removed dispatch release GHA from ${{ github.event.inputs.target_branch }} as we are releasing only master changes + - Airgun and Nailgun Requirements uses ${{ github.event.inputs.target_branch }} branch + - Constants are using new version now + - Stream tests removed + - Setup.py uses new version + " + pr_number=$(gh pr create --title "$title" --body "$body" --base "${{ github.event.inputs.target_branch }}" | awk -F'/' '{print $NF}') + echo "$pr_number" + echo "pr_number=$pr_number" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: Add the prt comment for running the sanity tests + id: add-parent-prt-comment + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + trigger: test-robottelo + pr_number: ${{ steps.create_pr.outputs.pr_number }} + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: add the no-cherrypick label + uses: actions/github-script@v7 + with: + github-token: ${{ secrets._REPO_ADMIN_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: ${{ steps.create_pr.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["No-CherryPick"] + }) + + branch-protection: + runs-on: ubuntu-latest + needs: auto-branching-new-downstream-release + if: success() + steps: + - name: Create branch protection + run: | + TOKEN=${{ secrets._REPO_ADMIN_TOKEN }} + OWNER=${{ github.repository_owner }} + REPO=${{ github.event.repository.name }} + BRANCH="${{ github.event.inputs.target_branch }}" # Adjust branch name as needed + # Branch protection payload + PROTECTION_PAYLOAD='{ + "required_status_checks": { + "strict": true, + "contexts": ["Code Quality (3.10)", "Code Quality (3.11)", "Code Quality (3.12)", "Enforcing cherrypick labels"] + }, + "required_linear_history": true, + "enforce_admins": null, + "required_pull_request_reviews": null, + "restrictions": null, + "allow_force_pushes": null, + "allow_deletions": null + }' + # Call GitHub API to update branch protection + PROTECTION_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X PUT \ + -H "Accept: application/vnd.github.luke-cage-preview+json" \ + -H "Authorization: token $TOKEN" \ + -d "$PROTECTION_PAYLOAD" \ + "https://api.github.com/repos/$OWNER/$REPO/branches/$BRANCH/protection") + + if [[ $PROTECTION_RESPONSE -eq 200 ]]; then + echo "Branch protection successfully updated." + echo "protection-outcome=success" >> "$GITHUB_OUTPUT" + else + echo "Failed to update branch protection. HTTP status code: $PROTECTION_RESPONSE" + echo "protection-outcome=failure" >> "$GITHUB_OUTPUT" + exit 1 + fi + + auto-branching-master: + name: master - raise PR with changes + runs-on: ubuntu-latest + needs: check-group-membership + if: ${{ needs.check-group-membership.outputs.member == 'true' }} + + steps: + - name: Checkout Robottelo + uses: actions/checkout@v4 + + - name: Update target branch label in dependabot yml file + id: update-dependabot + run: | + # Read the dependabot.yml file + FILE_PATH="./.github/dependabot.yml" + TARGET_BRANCH="${{ github.event.inputs.target_branch }}" + # Append the target branch label to the labels node + awk -v target="'$TARGET_BRANCH'" '/^ *labels:/ {$0 = $0 "\n - " target} 1' "$FILE_PATH" > temp.yml && mv temp.yml "$FILE_PATH" + + - name: Remove lines with @pytest.mark.stream + id: remove-mark-stream + run: | + # Loop through files in the folder + grep -rl "tests/foreman" -e '@pytest\.mark\.stream' | while IFS= read -r file; do + awk '!/@pytest\.mark\.stream/' "$file" > temp && mv temp "$file" + done + + - name: Update the Constants in __init__.py file + run: | + version="${{ github.event.inputs.target_branch }}" + ga_version="${{ github.event.inputs.ga_version }}" + old_stream_version="${version%.z}" + new_stream_version="${{ github.event.inputs.stream_version }}" + non_ga_versions="['$old_stream_version', '$new_stream_version']" + FILE_PATH="./robottelo/constants/__init__.py" + # update the version + sed -i.bak "s/SATELLITE_VERSION = \"$old_stream_version\"/SATELLITE_VERSION = \"$new_stream_version\"/" "$FILE_PATH" + sed -i.bak "s/ SATELLITE_VERSION: \"$old_stream_version\"/ SATELLITE_VERSION: \"$new_stream_version\"/" ./conf/robottelo.yaml.template + sed -i.bak "s/SAT_NON_GA_VERSIONS = \[.*\]/SAT_NON_GA_VERSIONS = $non_ga_versions/" "$FILE_PATH" + rm "$FILE_PATH.bak" "./conf/robottelo.yaml.template.bak" + + - name: git status + run: git status + + - name: git diff + run: git diff + + - name: Commit changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + branch_name="auto-branching-${{ github.event.inputs.target_branch }}-$(date '+%s')" + git checkout -b "$branch_name" + git add setup.py ./tests/foreman ./robottelo/* ./requirements.txt ./.github/* ./conf/robottelo.yaml.template + git commit -m "Changes for new ${{ github.event.inputs.target_branch }} branch" + git remote -vvv + git push origin "$branch_name" + + - name: Create pull request + id: create_pr + run: | + title="[master]: Changes for new ${{ github.event.inputs.target_branch }} branch" + body=" + ### Problem Statement + New ${{ github.event.inputs.target_branch }} downstream and master points to stream that is ${{ github.event.inputs.stream_version }} + ### Solution + - Dependabot.yaml cherrypicks to ${{ github.event.inputs.target_branch }} + - Robottelo conf and constants now uses ${{ github.event.inputs.stream_version }} and ${{ github.event.inputs.target_branch }} satellite versions + " + pr_number=$(gh pr create --title "$title" --body "$body" --base "master" | awk -F'/' '{print $NF}') + echo "$pr_number" + echo "pr_number=$pr_number" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: Add the prt comment for running the sanity tests + id: add-parent-prt-comment + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + trigger: test-robottelo + pr_number: ${{ steps.create_pr.outputs.pr_number }} + GITHUB_TOKEN: ${{ secrets._REPO_ADMIN_TOKEN }} + + - name: add the no-cherrypick label + uses: actions/github-script@v7 + with: + github-token: ${{ secrets._REPO_ADMIN_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: ${{ steps.create_pr.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["No-CherryPick"] + }) diff --git a/.github/workflows/auto_cherry_pick_merge.yaml b/.github/workflows/auto_cherry_pick_merge.yaml index 5ee0c3b7bad..63a579e3ed7 100644 --- a/.github/workflows/auto_cherry_pick_merge.yaml +++ b/.github/workflows/auto_cherry_pick_merge.yaml @@ -40,7 +40,7 @@ jobs: - name: Wait for other status checks to Pass id: waitforstatuschecks - uses: lewagon/wait-on-check-action@v1.3.3 + uses: lewagon/wait-on-check-action@v1.3.4 with: ref: ${{ github.head_ref }} repo-token: ${{ secrets.CHERRYPICK_PAT }} diff --git a/.github/workflows/dependency_merge.yml b/.github/workflows/dependency_merge.yml index f76d5a622dd..f549c5c6e69 100644 --- a/.github/workflows/dependency_merge.yml +++ b/.github/workflows/dependency_merge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1 + uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" @@ -51,7 +51,7 @@ jobs: - name: Wait for other status checks to Pass id: waitforstatuschecks - uses: lewagon/wait-on-check-action@v1.3.3 + uses: lewagon/wait-on-check-action@v1.3.4 with: ref: ${{ github.head_ref }} repo-token: ${{ secrets.CHERRYPICK_PAT }} diff --git a/.github/workflows/prt_labels.yml b/.github/workflows/prt_labels.yml index 311516fa682..52dd68589b3 100644 --- a/.github/workflows/prt_labels.yml +++ b/.github/workflows/prt_labels.yml @@ -1,7 +1,7 @@ name: Remove the PRT label, for the new commit on: - pull_request: + pull_request_target: types: ["synchronize"] jobs: diff --git a/conf/oscap.yaml.template b/conf/oscap.yaml.template index bfeec7103fb..f23ec46ffb9 100644 --- a/conf/oscap.yaml.template +++ b/conf/oscap.yaml.template @@ -1,2 +1,4 @@ OSCAP: CONTENT_PATH: /usr/share/xml/scap/ssg/content/ssg-rhel7-ds.xml + # see: robottelo/constants/__init__.py OSCAP_PROFILE + PROFILE: security7 diff --git a/pytest_fixtures/component/http_proxy.py b/pytest_fixtures/component/http_proxy.py index 8c6095092dd..a98e7409699 100644 --- a/pytest_fixtures/component/http_proxy.py +++ b/pytest_fixtures/component/http_proxy.py @@ -1,6 +1,13 @@ import pytest from robottelo.config import settings +from robottelo.hosts import ProxyHost + + +@pytest.fixture(scope='session') +def session_auth_proxy(session_target_sat): + """Instantiates authenticated HTTP proxy as a session-scoped fixture""" + return ProxyHost(settings.http_proxy.auth_proxy_url) @pytest.fixture diff --git a/pytest_fixtures/component/oscap.py b/pytest_fixtures/component/oscap.py index e8a7d230603..786788914f2 100644 --- a/pytest_fixtures/component/oscap.py +++ b/pytest_fixtures/component/oscap.py @@ -39,7 +39,7 @@ def scap_content(import_ansible_roles, module_target_sat): scap_profile_id = [ profile['id'] for profile in scap_info.scap_content_profiles - if OSCAP_PROFILE['security7'] in profile['title'] + if OSCAP_PROFILE[settings.oscap.profile] in profile['title'] ][0] return { "title": title, diff --git a/pytest_fixtures/core/contenthosts.py b/pytest_fixtures/core/contenthosts.py index 485591ed7d9..d2bbb4cce39 100644 --- a/pytest_fixtures/core/contenthosts.py +++ b/pytest_fixtures/core/contenthosts.py @@ -45,6 +45,15 @@ def rhel_contenthost(request): yield host +@pytest.fixture(scope='module') +def module_rhel_contenthost(request): + """A module-level fixture that provides a content host object parametrized""" + # Request should be parametrized through pytest_fixtures.fixture_markers + # unpack params dict + with Broker(**host_conf(request), host_class=ContentHost) as host: + yield host + + @pytest.fixture(params=[{'rhel_version': '7'}]) def rhel7_contenthost(request): """A function-level fixture that provides a rhel7 content host object""" @@ -161,6 +170,16 @@ def rex_contenthost(request, module_org, target_sat, module_ak_with_cv): yield host +@pytest.fixture +def rex_contenthosts(request, module_org, target_sat, module_ak_with_cv): + request.param['no_containers'] = True + with Broker(**host_conf(request), host_class=ContentHost, _count=2) as hosts: + for host in hosts: + repo = settings.repos['SATCLIENT_REPO'][f'RHEL{host.os_version.major}'] + host.register(module_org, None, module_ak_with_cv.name, target_sat, repo=repo) + yield hosts + + @pytest.fixture def katello_host_tools_tracer_host(rex_contenthost, target_sat): """Install katello-host-tools-tracer, create custom @@ -268,8 +287,10 @@ def sat_upgrade_chost(): def custom_host(request): """A rhel content host that passes custom host config through request.param""" deploy_args = request.param - # if 'deploy_rhel_version' is not set, let's default to RHEL 8 - deploy_args['deploy_rhel_version'] = deploy_args.get('deploy_rhel_version', '8') + # if 'deploy_rhel_version' is not set, let's default to what's in content_host.yaml + deploy_args['deploy_rhel_version'] = deploy_args.get( + 'deploy_rhel_version', settings.content_host.default_rhel_version + ) deploy_args['workflow'] = 'deploy-rhel' with Broker(**deploy_args, host_class=Satellite) as host: yield host diff --git a/pytest_plugins/fixture_markers.py b/pytest_plugins/fixture_markers.py index f1e87d3b7f7..909e7a53634 100644 --- a/pytest_plugins/fixture_markers.py +++ b/pytest_plugins/fixture_markers.py @@ -5,11 +5,13 @@ TARGET_FIXTURES = [ 'rhel_contenthost', + 'module_rhel_contenthost', 'content_hosts', 'module_provisioning_rhel_content', 'capsule_provisioning_rhel_content', 'module_sync_kickstart_content', 'rex_contenthost', + 'rex_contenthosts', ] diff --git a/requirements-optional.txt b/requirements-optional.txt index e776154c7a1..9cc9c0300f1 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,8 +1,8 @@ # For running tests and checking code quality using these modules. flake8==7.0.0 -pytest-cov==4.1.0 +pytest-cov==5.0.0 redis==5.0.3 -pre-commit==3.6.2 +pre-commit==3.7.0 # For generating documentation. sphinx==7.2.6 diff --git a/requirements.txt b/requirements.txt index 73f21ac47e3..4232d0513e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,40 +1,34 @@ # Version updates managed by dependabot +apypie==0.4.0 betelgeuse==1.11.0 -# broker[docker]==0.4.1 - Temporarily disabled, see below +broker[docker]==0.4.9 cryptography==42.0.5 deepdiff==6.7.1 -docker==7.0.0 # Temporary until Broker is back on PyPi dynaconf[vault]==3.2.5 -fauxfactory==3.1.0 +fauxfactory==3.1.1 jinja2==3.1.3 manifester==0.0.14 navmazing==1.2.2 -paramiko==3.4.0 # Temporary until Broker is back on PyPi productmd==1.38 pyotp==2.9.0 python-box==7.1.1 pytest==8.1.1 -pytest-order==1.2.0 +pytest-order==1.2.1 pytest-services==2.2.1 -pytest-mock==3.12.0 -pytest-reportportal==5.4.0 +pytest-mock==3.14.0 +pytest-reportportal==5.4.1 pytest-xdist==3.5.0 pytest-fixturecollection==0.1.2 pytest-ibutsu==2.2.4 PyYAML==6.0.1 requests==2.31.0 tenacity==8.2.3 -testimony==2.3.0 +testimony==2.4.0 wait-for==1.2.0 wrapanapi==3.6.0 # Get airgun, nailgun and upgrade from master -git+https://github.com/SatelliteQE/airgun.git@master#egg=airgun -git+https://github.com/SatelliteQE/nailgun.git@master#egg=nailgun -# Broker currently is unable to push to PyPi due to [1] and [2] -# In the meantime, we install directly from the repo -# [1] - https://github.com/ParallelSSH/ssh2-python/issues/193 -# [2] - https://github.com/pypi/warehouse/issues/7136 -git+https://github.com/SatelliteQE/broker.git@0.4.7#egg=broker +airgun @ git+https://github.com/SatelliteQE/airgun.git@master#egg=airgun +nailgun @ git+https://github.com/SatelliteQE/nailgun.git@master#egg=nailgun --editable . diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index f598821839d..013be110fe0 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -225,7 +225,12 @@ Validator( 'oscap.content_path', must_exist=True, - ) + ), + Validator( + 'oscap.profile', + default='security7', + must_exist=True, + ), ], osp=[ Validator( diff --git a/robottelo/constants/__init__.py b/robottelo/constants/__init__.py index 90991ce3df2..8bda8f3e777 100644 --- a/robottelo/constants/__init__.py +++ b/robottelo/constants/__init__.py @@ -263,6 +263,11 @@ class Colored(Box): 'rhel-8-for-x86_64-appstream-rpms', ) +OHSNAP_RHEL9_REPOS = ( + 'rhel-9-for-x86_64-baseos-rpms', + 'rhel-9-for-x86_64-appstream-rpms', +) + # On importing manifests, Red Hat repositories are listed like this: # Product -> RepositorySet -> Repository # We need to first select the Product, then the reposet and then the repos @@ -1089,6 +1094,12 @@ class Colored(Box): 'destroy_locations', 'assign_locations', ], + 'LookupValue': [ + 'edit_lookup_values', + 'create_lookup_values', + 'destroy_lookup_values', + 'view_lookup_values', + ], 'MailNotification': ['view_mail_notifications', 'edit_user_mail_notifications'], 'Medium': ['view_media', 'create_media', 'edit_media', 'destroy_media'], 'Model': ['view_models', 'create_models', 'edit_models', 'destroy_models'], @@ -1120,7 +1131,7 @@ class Colored(Box): 'lock_ptables', ], 'Realm': ['view_realms', 'create_realms', 'edit_realms', 'destroy_realms'], - 'RemoteExecutionFeature': ['edit_remote_execution_features'], + 'RemoteExecutionFeature': ['view_remote_execution_features', 'edit_remote_execution_features'], 'ReportTemplate': [ 'edit_report_templates', 'destroy_report_templates', @@ -1650,63 +1661,20 @@ class Colored(Box): 'Viewer', ] -BOOKMARK_ENTITIES = [ +BOOKMARK_ENTITIES_SELECTION = [ { 'name': 'ActivationKey', 'controller': 'katello_activation_keys', 'session_name': 'activationkey', + 'old_ui': True, }, - {'name': 'Dashboard', 'controller': 'dashboard', 'session_name': 'dashboard'}, - {'name': 'Audit', 'controller': 'audits', 'session_name': 'audit'}, - { - 'name': 'Report', - 'controller': 'config_reports', - 'setup': entities.Report, - 'session_name': 'configreport', - }, - {'name': 'Task', 'controller': 'foreman_tasks_tasks', 'session_name': 'task'}, - # TODO Load manifest for the test_positive_end_to_end from the ui/test_bookmarks.py - # {'name': 'Subscriptions', 'controller': 'subscriptions','session_name': 'subscription' }, - {'name': 'Product', 'controller': 'katello_products', 'session_name': 'product'}, - {'name': 'Repository', 'controller': 'katello_repositories', 'session_name': 'repository'}, - { - 'name': 'ContentCredential', - 'controller': 'katello_content_credentials', - 'session_name': 'contentcredential', - }, - {'name': 'SyncPlan', 'controller': 'katello_sync_plans', 'session_name': 'syncplan'}, - {'name': 'ContentView', 'controller': 'katello_content_views', 'session_name': 'contentview'}, - {'name': 'Errata', 'controller': 'katello_errata', 'session_name': 'errata'}, - {'name': 'Package', 'controller': 'katello_erratum_packages', 'session_name': 'package'}, - { - 'name': 'ContainerImageTag', - 'controller': 'katello_docker_tags', - 'session_name': 'containerimagetag', - }, + {'name': 'Errata', 'controller': 'katello_errata', 'session_name': 'errata', 'old_ui': True}, {'name': 'Host', 'controller': 'hosts', 'setup': entities.Host, 'session_name': 'host_new'}, - {'name': 'ContentHost', 'controller': 'hosts', 'session_name': 'contenthost'}, { - 'name': 'HostCollection', - 'controller': 'katello_host_collections', - 'session_name': 'hostcollection', - }, - {'name': 'Architecture', 'controller': 'architectures', 'session_name': 'architecture'}, - { - 'name': 'HardwareModel', - 'controller': 'models', - 'setup': entities.Model, - 'session_name': 'hardwaremodel', - }, - { - 'name': 'InstallationMedia', - 'controller': 'media', - 'session_name': 'media', - 'setup': entities.Media, - }, - { - 'name': 'OperatingSystem', - 'controller': 'operatingsystems', - 'session_name': 'operatingsystem', + 'name': 'UserGroup', + 'controller': 'usergroups', + 'setup': entities.UserGroup, + 'session_name': 'usergroup', }, { 'name': 'PartitionTable', @@ -1714,65 +1682,24 @@ class Colored(Box): 'setup': entities.PartitionTable, 'session_name': 'partitiontable', }, + { + 'name': 'Product', + 'controller': 'katello_products', + 'session_name': 'product', + 'old_ui': True, + }, { 'name': 'ProvisioningTemplate', 'controller': 'provisioning_templates', 'session_name': 'provisioningtemplate', }, - { - 'name': 'HostGroup', - 'controller': 'hostgroups', - 'setup': entities.HostGroup, - 'session_name': 'hostgroup', - }, - { - 'name': 'DiscoveryRule', - 'controller': 'discovery_rules', - 'setup': entities.DiscoveryRule, - 'session_name': 'discoveryrule', - }, - { - 'name': 'GlobalParameter', - 'controller': 'common_parameters', - 'setup': entities.CommonParameter, - 'skip_for_ui': True, - }, - {'name': 'Role', 'controller': 'ansible_roles', 'setup': entities.Role, 'session_name': 'role'}, - {'name': 'Variables', 'controller': 'ansible_variables', 'session_name': 'ansiblevariables'}, - {'name': 'Capsules', 'controller': 'smart_proxies', 'session_name': 'capsule'}, - { - 'name': 'ComputeResource', - 'controller': 'compute_resources', - 'setup': entities.LibvirtComputeResource, - 'session_name': 'computeresource', - }, - { - 'name': 'ComputeProfile', - 'controller': 'compute_profiles', - 'setup': entities.ComputeProfile, - 'session_name': 'computeprofile', - }, - {'name': 'Subnet', 'controller': 'subnets', 'setup': entities.Subnet, 'session_name': 'subnet'}, - {'name': 'Domain', 'controller': 'domains', 'setup': entities.Domain, 'session_name': 'domain'}, - {'name': 'Realm', 'controller': 'realms', 'setup': entities.Realm, 'session_name': 'realm'}, - {'name': 'Location', 'controller': 'locations', 'session_name': 'location'}, - {'name': 'Organization', 'controller': 'organizations', 'session_name': 'organization'}, - {'name': 'User', 'controller': 'users', 'session_name': 'user'}, - { - 'name': 'UserGroup', - 'controller': 'usergroups', - 'setup': entities.UserGroup, - 'session_name': 'usergroup', - }, - {'name': 'Role', 'controller': 'roles', 'session_name': 'role'}, - {'name': 'Settings', 'controller': 'settings', 'session_name': 'settings'}, + {'name': 'Repository', 'controller': 'katello_repositories', 'session_name': 'repository'}, ] STRING_TYPES = ['alpha', 'numeric', 'alphanumeric', 'latin1', 'utf8', 'cjk', 'html'] VMWARE_CONSTANTS = { 'folder': 'vm', - 'guest_os': 'Red Hat Enterprise Linux 8 (64 bit)', 'scsicontroller': 'LSI Logic Parallel', 'virtualhw_version': 'Default', 'pool': 'Resources', @@ -2130,6 +2057,7 @@ class Colored(Box): 'option is not present in the /etc/dnf/dnf.conf' ) +EXPIRED_MANIFEST = 'expired-manifest.zip' # Data File Paths class DataFile(Box): @@ -2150,3 +2078,4 @@ class DataFile(Box): PARTITION_SCRIPT_DATA_FILE = DATA_DIR.joinpath(PARTITION_SCRIPT_DATA_FILE) OS_TEMPLATE_DATA_FILE = DATA_DIR.joinpath(OS_TEMPLATE_DATA_FILE) FAKE_3_YUM_REPO_RPMS_ANT = DATA_DIR.joinpath(FAKE_3_YUM_REPO_RPMS[0]) + EXPIRED_MANIFEST_FILE = DATA_DIR.joinpath(EXPIRED_MANIFEST) diff --git a/robottelo/constants/repos.py b/robottelo/constants/repos.py index 9738894a689..a0b2a8adeec 100644 --- a/robottelo/constants/repos.py +++ b/robottelo/constants/repos.py @@ -25,6 +25,6 @@ FAKE_0_YUM_REPO_STRING_BASED_VERSIONS = ( 'https://fixtures.pulpproject.org/rpm-string-version-updateinfo/' ) - +FAKE_ZST_REPO = 'https://fixtures.pulpproject.org/rpm-zstd-metadata' ANSIBLE_GALAXY = 'https://galaxy.ansible.com/' ANSIBLE_HUB = 'https://cloud.redhat.com/api/automation-hub/' diff --git a/robottelo/hosts.py b/robottelo/hosts.py index 7596411fa56..a5d5e2319f7 100644 --- a/robottelo/hosts.py +++ b/robottelo/hosts.py @@ -14,6 +14,7 @@ from urllib.parse import urljoin, urlparse, urlunsplit import warnings +import apypie from box import Box from broker import Broker from broker.hosts import Host @@ -170,6 +171,10 @@ class IPAHostError(Exception): pass +class ProxyHostError(Exception): + pass + + class ContentHost(Host, ContentHostMixins): run = Host.execute default_timeout = settings.server.ssh_client.command_timeout @@ -1762,6 +1767,7 @@ def __init__(self, hostname=None, **kwargs): # create dummy classes for later population self._api = type('api', (), {'_configured': False}) self._cli = type('cli', (), {'_configured': False}) + self._apidoc = None self.record_property = None def _swap_nailgun(self, new_version): @@ -1815,6 +1821,19 @@ class DecClass(cls): self._api._configured = True return self._api + @property + def apidoc(self): + """Provide Satellite's apidoc via apypie""" + if not self._apidoc: + self._apidoc = apypie.Api( + uri=self.url, + username=settings.server.admin_username, + password=settings.server.admin_password, + api_version=2, + verify_ssl=settings.server.verify_ca, + ).apidoc + return self._apidoc + @property def cli(self): """Import all robottelo cli entities and wrap them under self.cli""" @@ -2545,3 +2564,42 @@ def remove_user_from_usergroup(self, member_username, member_group): ) if result.status != 0: raise IPAHostError('Failed to remove the user from user group') + + +class ProxyHost(Host): + """Class representing HTTP Proxy host""" + + def __init__(self, url, **kwargs): + self._conf_dir = '/etc/squid/' + self._access_log = '/var/log/squid/access.log' + kwargs['hostname'] = urlparse(url).hostname + super().__init__(**kwargs) + + def add_user(self, name, passwd): + """Adds new user to the HTTP Proxy""" + res = self.execute(f"htpasswd -b {self._conf_dir}passwd {name} '{passwd}'") + assert res.status == 0, f'User addition failed on the proxy side: {res.stderr}' + return res + + def remove_user(self, name): + """Removes a user from HTTP Proxy""" + res = self.execute(f'htpasswd -D {self._conf_dir}passwd {name}') + assert res.status == 0, f'User deletion failed on the proxy side: {res.stderr}' + return res + + def get_log(self, which=None, tail=None, grep=None): + """Returns log content from the HTTP Proxy instance + + :param which: Which log file should be read. Defaults to access.log. + :param tail: Use when only the tail of a long log file is needed. + :param grep: Grep for some expression. + :return: Log content found or None + """ + log_file = which or self._access_log + cmd = f'tail -n {tail} {log_file}' if tail else f'cat {log_file}' + if grep: + cmd = f'{cmd} | grep "{grep}"' + res = self.execute(cmd) + if res.status != 0: + raise ProxyHostError(f'Proxy log read failed: {res.stderr}') + return None if res.stdout == '' else res.stdout diff --git a/robottelo/utils/virtwho.py b/robottelo/utils/virtwho.py index e159a487394..b1cac0012ca 100644 --- a/robottelo/utils/virtwho.py +++ b/robottelo/utils/virtwho.py @@ -210,7 +210,7 @@ def check_message_in_rhsm_log(message): """Check the message exist in /var/log/rhsm/rhsm.log""" wait_for( lambda: 'Host-to-guest mapping being sent to' in get_rhsm_log(), - timeout=10, + timeout=20, delay=2, ) logs = get_rhsm_log() @@ -230,7 +230,7 @@ def _get_hypervisor_mapping(hypervisor_type): """ wait_for( lambda: 'Host-to-guest mapping being sent to' in get_rhsm_log(), - timeout=10, + timeout=20, delay=2, ) logs = get_rhsm_log() diff --git a/tests/foreman/api/test_ansible.py b/tests/foreman/api/test_ansible.py index 515d432423f..435501d0696 100644 --- a/tests/foreman/api/test_ansible.py +++ b/tests/foreman/api/test_ansible.py @@ -51,163 +51,313 @@ def filtered_user(target_sat, module_org, module_location): return user, password -@pytest.fixture -def rex_host_in_org_and_loc(target_sat, module_org, module_location, rex_contenthost): - host = target_sat.api.Host().search(query={'search': f'name={rex_contenthost.hostname}'})[0] - target_sat.api.Host(id=host.id, organization=[module_org.id]).update(['organization']) - target_sat.api.Host(id=host.id, location=module_location.id).update(['location']) - return host +@pytest.mark.upgrade +class TestAnsibleCfgMgmt: + """Test class for Configuration Management with Ansible + :CaseComponent: Ansible-ConfigurationManagement -@pytest.mark.e2e -def test_fetch_and_sync_ansible_playbooks(target_sat): """ - Test Ansible Playbooks api for fetching and syncing playbooks - - :id: 17b4e767-1494-4960-bc60-f31a0495c09f - :customerscenario: true - - :steps: + @pytest.mark.e2e + def test_fetch_and_sync_ansible_playbooks(self, target_sat): + """ + Test Ansible Playbooks api for fetching and syncing playbooks - 1. Install ansible collection with playbooks. - 2. Try to fetch the playbooks via api. - 3. Sync the playbooks. - 4. Assert the count of playbooks fetched and synced are equal. + :id: 17b4e767-1494-4960-bc60-f31a0495c09f - :expectedresults: - 1. Playbooks should be fetched and synced successfully. + :steps: + 1. Install ansible collection with playbooks. + 2. Try to fetch the playbooks via api. + 3. Sync the playbooks. + 4. Assert the count of playbooks fetched and synced are equal. - :BZ: 2115686 - """ - target_sat.execute( - "ansible-galaxy collection install -p /usr/share/ansible/collections " - "xprazak2.forklift_collection" - ) - proxy_id = target_sat.nailgun_smart_proxy.id - playbook_fetch = target_sat.api.AnsiblePlaybooks().fetch(data={'proxy_id': proxy_id}) - playbooks_count = len(playbook_fetch['results']['playbooks_names']) - playbook_sync = target_sat.api.AnsiblePlaybooks().sync(data={'proxy_id': proxy_id}) - assert playbook_sync['action'] == "Sync playbooks" - - target_sat.wait_for_tasks( - search_query=(f'id = {playbook_sync["id"]}'), - poll_timeout=100, - ) - task_details = target_sat.api.ForemanTask().search( - query={'search': f'id = {playbook_sync["id"]}'} - ) - assert task_details[0].result == 'success' - assert len(task_details[0].output['result']['created']) == playbooks_count + :expectedresults: + 1. Playbooks should be fetched and synced successfully. + :BZ: 2115686 -@pytest.mark.e2e -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('[^6].*') -def test_positive_ansible_job_on_host( - target_sat, module_org, module_location, module_ak_with_synced_repo, rhel_contenthost -): - """ - Test successful execution of Ansible Job on host. - - :id: c8dcdc54-cb98-4b24-bff9-049a6cc36acb - - :steps: - 1. Register a content host with satellite - 2. Import a role into satellite - 3. Assign that role to a host - 4. Assert that the role was assigned to the host successfully - 5. Run the Ansible playbook associated with that role - 6. Check if the job is executed. - - :expectedresults: - 1. Host should be assigned the proper role. - 2. Job execution must be successful. - - :BZ: 2164400 - - :CaseComponent: Ansible-RemoteExecution - """ - SELECTED_ROLE = 'RedHatInsights.insights-client' - if rhel_contenthost.os_version.major <= 7: - rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) - assert rhel_contenthost.execute('yum install -y insights-client').status == 0 - result = rhel_contenthost.register( - module_org, module_location, module_ak_with_synced_repo.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(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) - role_id = target_sat.api.AnsibleRoles().search(query={'search': f'name={SELECTED_ROLE}'})[0].id - target_sat.api.Host(id=target_host.id).add_ansible_role(data={'ansible_role_id': role_id}) - host_roles = target_host.list_ansible_roles() - assert host_roles[0]['name'] == SELECTED_ROLE - assert target_host.name == rhel_contenthost.hostname - - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name = {rhel_contenthost.hostname}', - }, - ) - 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() - assert result.succeeded == 1 - target_sat.api.Host(id=target_host.id).remove_ansible_role(data={'ansible_role_id': role_id}) - host_roles = target_host.list_ansible_roles() - assert len(host_roles) == 0 + :customerscenario: true + """ + target_sat.execute( + "ansible-galaxy collection install -p /usr/share/ansible/collections " + "xprazak2.forklift_collection" + ) + proxy_id = target_sat.nailgun_smart_proxy.id + playbook_fetch = target_sat.api.AnsiblePlaybooks().fetch(data={'proxy_id': proxy_id}) + playbooks_count = len(playbook_fetch['results']['playbooks_names']) + playbook_sync = target_sat.api.AnsiblePlaybooks().sync(data={'proxy_id': proxy_id}) + assert playbook_sync['action'] == "Sync playbooks" + + target_sat.wait_for_tasks( + search_query=(f'id = {playbook_sync["id"]}'), + poll_timeout=100, + ) + task_details = target_sat.api.ForemanTask().search( + query={'search': f'id = {playbook_sync["id"]}'} + ) + assert task_details[0].result == 'success' + assert len(task_details[0].output['result']['created']) == playbooks_count + + @pytest.mark.e2e + @pytest.mark.tier2 + def test_add_and_remove_ansible_role_hostgroup(self, target_sat): + """ + Test add and remove functionality for ansible roles in hostgroup via API + + :id: 7672cf86-fa31-11ed-855a-0fd307d2d66b + + :steps: + 1. Create a hostgroup and a nested hostgroup + 2. Sync a few ansible roles + 3. Assign a few ansible roles with the host group + 4. Add some ansible role with the host group + 5. Add some ansible roles to the nested hostgroup + 6. Remove the added ansible roles from the parent and nested hostgroup + + :expectedresults: + 1. Ansible role assign/add/remove functionality should work as expected in API + + :BZ: 2164400 + """ + ROLE_NAMES = [ + 'theforeman.foreman_scap_client', + 'redhat.satellite.hostgroups', + 'RedHatInsights.insights-client', + 'redhat.satellite.compute_resources', + ] + hg = target_sat.api.HostGroup(name=gen_string('alpha')).create() + hg_nested = target_sat.api.HostGroup(name=gen_string('alpha'), parent=hg).create() + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': ROLE_NAMES}) + ROLES = [ + target_sat.api.AnsibleRoles().search(query={'search': f'name={role}'})[0].id + for role in ROLE_NAMES + ] + # Assign first 2 roles to HG and verify it + target_sat.api.HostGroup(id=hg.id).assign_ansible_roles( + data={'ansible_role_ids': ROLES[:2]} + ) + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:2], strict=True + ): + assert r1['name'] == r2 + + # Add next role from list to HG and verify it + target_sat.api.HostGroup(id=hg.id).add_ansible_role(data={'ansible_role_id': ROLES[2]}) + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True + ): + assert r1['name'] == r2 + + # Add next role to nested HG, and verify roles are also nested to HG along with assigned role + # Also, ensure the parent HG does not contain the roles assigned to nested HGs + target_sat.api.HostGroup(id=hg_nested.id).add_ansible_role( + data={'ansible_role_id': ROLES[3]} + ) + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles(), + [ROLE_NAMES[-1]] + ROLE_NAMES[:-1], + strict=True, + ): + assert r1['name'] == r2 + + for r1, r2 in zip( + target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True + ): + assert r1['name'] == r2 + + # Remove roles assigned one by one from HG and nested HG + for role in ROLES[:3]: + target_sat.api.HostGroup(id=hg.id).remove_ansible_role(data={'ansible_role_id': role}) + hg_roles = target_sat.api.HostGroup(id=hg.id).list_ansible_roles() + assert len(hg_roles) == 0 + + for role in ROLES: + target_sat.api.HostGroup(id=hg_nested.id).remove_ansible_role( + data={'ansible_role_id': role} + ) + hg_nested_roles = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() + assert len(hg_nested_roles) == 0 + + @pytest.mark.e2e + @pytest.mark.tier2 + def test_positive_ansible_roles_inherited_from_hostgroup( + self, request, target_sat, module_org, module_location + ): + """Verify ansible roles inheritance functionality for host with parent/nested hostgroup via API + :id: 7672cf86-fa31-11ed-855a-0fd307d2d66g -@pytest.mark.no_containers -def test_positive_ansible_job_on_multiple_host( - target_sat, - module_org, - rhel9_contenthost, - rhel8_contenthost, - rhel7_contenthost, - module_location, - module_ak_with_synced_repo, -): - """Test execution of Ansible job on multiple hosts simultaneously. + :steps: + 1. Create a host, hostgroup and nested hostgroup + 2. Sync a few ansible roles + 3. Assign a few ansible roles to the host, hostgroup, nested hostgroup and verify it. + 4. Update host to be in parent/nested hostgroup and verify roles assigned - :id: 9369feef-466c-40d3-9d0d-65520d7f21ef + :expectedresults: + 1. Hosts in parent/nested hostgroups must have direct and indirect roles correctly assigned. - :customerscenario: true + :BZ: 2187967 - :steps: - 1. Register multiple content hosts with satellite - 2. Import a role into satellite - 3. Assign that role to all host - 4. Trigger ansible job keeping all host in a single query - 5. Check the passing and failing of individual hosts - 6. Check if one of the job on a host is failed resulting into whole job is marked as failed. + :customerscenario: true + """ + ROLE_NAMES = [ + 'theforeman.foreman_scap_client', + 'RedHatInsights.insights-client', + 'redhat.satellite.compute_resources', + ] + proxy_id = target_sat.nailgun_smart_proxy.id + host = target_sat.api.Host(organization=module_org, location=module_location).create() + hg = target_sat.api.HostGroup(name=gen_string('alpha'), organization=[module_org]).create() + hg_nested = target_sat.api.HostGroup( + name=gen_string('alpha'), parent=hg, organization=[module_org] + ).create() + + @request.addfinalizer + def _finalize(): + host.delete() + hg_nested.delete() + hg.delete() + + target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': ROLE_NAMES}) + ROLES = [ + target_sat.api.AnsibleRoles().search(query={'search': f'name={role}'})[0].id + for role in ROLE_NAMES + ] + + # Assign roles to Host/Hostgroup/Nested Hostgroup and verify it + target_sat.api.Host(id=host.id).add_ansible_role(data={'ansible_role_id': ROLES[0]}) + assert ROLE_NAMES[0] == target_sat.api.Host(id=host.id).list_ansible_roles()[0]['name'] + + target_sat.api.HostGroup(id=hg.id).add_ansible_role(data={'ansible_role_id': ROLES[1]}) + assert ROLE_NAMES[1] == target_sat.api.HostGroup(id=hg.id).list_ansible_roles()[0]['name'] + + target_sat.api.HostGroup(id=hg_nested.id).add_ansible_role( + data={'ansible_role_id': ROLES[2]} + ) + listroles = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() + assert ROLE_NAMES[2] == listroles[0]['name'] + assert listroles[0]['directly_assigned'] + assert ROLE_NAMES[1] == listroles[1]['name'] + assert not listroles[1]['directly_assigned'] + + # Update host to be in nested hostgroup and verify roles assigned + host.hostgroup = hg_nested + host = host.update(['hostgroup']) + listroles_host = target_sat.api.Host(id=host.id).list_ansible_roles() + assert ROLE_NAMES[0] == listroles_host[0]['name'] + assert listroles_host[0]['directly_assigned'] + assert ROLE_NAMES[1] == listroles_host[1]['name'] + assert not listroles_host[1]['directly_assigned'] + assert ROLE_NAMES[2] == listroles_host[2]['name'] + assert not listroles_host[1]['directly_assigned'] + # Verify nested hostgroup doesn't contains the roles assigned to host + listroles_nested_hg = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() + assert ROLE_NAMES[0] not in [role['name'] for role in listroles_nested_hg] + assert ROLE_NAMES[2] == listroles_nested_hg[0]['name'] + assert ROLE_NAMES[1] == listroles_nested_hg[1]['name'] + + # Update host to be in parent hostgroup and verify roles assigned + host.hostgroup = hg + host = host.update(['hostgroup']) + listroles = target_sat.api.Host(id=host.id).list_ansible_roles() + assert ROLE_NAMES[0] == listroles[0]['name'] + assert listroles[0]['directly_assigned'] + assert ROLE_NAMES[1] == listroles[1]['name'] + assert not listroles[1]['directly_assigned'] + # Verify parent hostgroup doesn't contains the roles assigned to host + listroles_hg = target_sat.api.HostGroup(id=hg.id).list_ansible_roles() + assert ROLE_NAMES[0] not in [role['name'] for role in listroles_hg] + assert ROLE_NAMES[1] == listroles_hg[0]['name'] + + @pytest.mark.rhel_ver_match('[78]') + @pytest.mark.tier2 + def test_positive_read_facts_with_filter( + self, target_sat, rex_contenthost, filtered_user, module_org, module_location + ): + """Read host's Ansible facts as a user with a role that has host filter + + :id: 483d5faf-7a4c-4cb7-b14f-369768ad99b0 + + :steps: + 1. Run Ansible roles on a host + 2. Using API, read Ansible facts of that host + + :expectedresults: Ansible facts returned + + :BZ: 1699188 + + :customerscenario: true + """ + user, password = filtered_user + host = rex_contenthost.nailgun_host + host.organization = module_org + host.location = module_location + host.update(['organization', 'location']) + + # gather ansible facts by running ansible roles on the host + host.play_ansible_roles() + if is_open('BZ:2216471'): + wait_for( + lambda: len(rex_contenthost.nailgun_host.get_facts()) > 0, + timeout=30, + delay=2, + ) + user_cfg = user_nailgun_config(user.login, password) + # get facts through API + user_facts = ( + target_sat.api.Host(server_config=user_cfg) + .search(query={'search': f'name={rex_contenthost.hostname}'})[0] + .get_facts() + ) + assert 'subtotal' in user_facts + assert user_facts['subtotal'] == 1 + assert 'results' in user_facts + assert rex_contenthost.hostname in user_facts['results'] + assert len(user_facts['results'][rex_contenthost.hostname]) > 0 - :expectedresults: - 1. One of the jobs failing on a single host must impact the overall result as failed. - :BZ: 2167396, 2190464, 2184117 +class TestAnsibleREX: + """Test class for remote execution via Ansible :CaseComponent: Ansible-RemoteExecution """ - hosts = [rhel9_contenthost, rhel8_contenthost, rhel7_contenthost] - SELECTED_ROLE = 'RedHatInsights.insights-client' - for host in hosts: - result = host.register( + + @pytest.mark.e2e + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('[^6].*') + def test_positive_ansible_job_on_host( + self, target_sat, module_org, module_location, module_ak_with_synced_repo, rhel_contenthost + ): + """Test successful execution of Ansible Job on host. + + :id: c8dcdc54-cb98-4b24-bff9-049a6cc36acb + + :steps: + 1. Register a content host with satellite + 2. Import a role into satellite + 3. Assign that role to a host + 4. Assert that the role was assigned to the host successfully + 5. Run the Ansible playbook associated with that role + 6. Check if the job is executed. + + :expectedresults: + 1. Host should be assigned the proper role. + 2. Job execution must be successful. + + :BZ: 2164400 + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' + if rhel_contenthost.os_version.major <= 7: + rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) + assert rhel_contenthost.execute('yum install -y insights-client').status == 0 + result = rhel_contenthost.register( module_org, module_location, module_ak_with_synced_repo.name, target_sat ) assert result.status == 0, f'Failed to register host: {result.stderr}' proxy_id = target_sat.nailgun_smart_proxy.id - target_host = host.nailgun_host + target_host = rhel_contenthost.nailgun_host target_sat.api.AnsibleRoles().sync( data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} ) @@ -217,153 +367,105 @@ def test_positive_ansible_job_on_multiple_host( target_sat.api.Host(id=target_host.id).add_ansible_role(data={'ansible_role_id': role_id}) host_roles = target_host.list_ansible_roles() assert host_roles[0]['name'] == SELECTED_ROLE + assert target_host.name == rhel_contenthost.hostname - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name ^ ({hosts[0].hostname} && {hosts[1].hostname} ' - f'&& {hosts[2].hostname})', - }, - ) - target_sat.wait_for_tasks( - f'resource_type = JobInvocation and resource_id = {job["id"]}', - poll_timeout=1000, - must_succeed=False, - ) - result = target_sat.api.JobInvocation(id=job['id']).read() - assert result.succeeded == 2 # SELECTED_ROLE working on rhel8/rhel9 clients - assert result.failed == 1 # SELECTED_ROLE failing on rhel7 client - assert result.status_label == 'failed' - - -@pytest.mark.e2e -@pytest.mark.tier2 -@pytest.mark.upgrade -def test_add_and_remove_ansible_role_hostgroup(target_sat): - """ - Test add and remove functionality for ansible roles in hostgroup via API - - :id: 7672cf86-fa31-11ed-855a-0fd307d2d66b - - :steps: - 1. Create a hostgroup and a nested hostgroup - 2. Sync a few ansible roles - 3. Assign a few ansible roles with the host group - 4. Add some ansible role with the host group - 5. Add some ansible roles to the nested hostgroup - 6. Remove the added ansible roles from the parent and nested hostgroup - - :expectedresults: - 1. Ansible role assign/add/remove functionality should work as expected in API - - :BZ: 2164400 - """ - ROLE_NAMES = [ - 'theforeman.foreman_scap_client', - 'redhat.satellite.hostgroups', - 'RedHatInsights.insights-client', - 'redhat.satellite.compute_resources', - ] - hg = target_sat.api.HostGroup(name=gen_string('alpha')).create() - hg_nested = target_sat.api.HostGroup(name=gen_string('alpha'), parent=hg).create() - proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': ROLE_NAMES}) - ROLES = [ - target_sat.api.AnsibleRoles().search(query={'search': f'name={role}'})[0].id - for role in ROLE_NAMES - ] - # Assign first 2 roles to HG and verify it - target_sat.api.HostGroup(id=hg.id).assign_ansible_roles(data={'ansible_role_ids': ROLES[:2]}) - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:2], strict=True - ): - assert r1['name'] == r2 - - # Add next role from list to HG and verify it - target_sat.api.HostGroup(id=hg.id).add_ansible_role(data={'ansible_role_id': ROLES[2]}) - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True - ): - assert r1['name'] == r2 - - # Add next role to nested HG, and verify roles are also nested to HG along with assigned role - # Also, ensure the parent HG does not contain the roles assigned to nested HGs - target_sat.api.HostGroup(id=hg_nested.id).add_ansible_role(data={'ansible_role_id': ROLES[3]}) - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles(), - [ROLE_NAMES[-1]] + ROLE_NAMES[:-1], - strict=True, - ): - assert r1['name'] == r2 - - for r1, r2 in zip( - target_sat.api.HostGroup(id=hg.id).list_ansible_roles(), ROLE_NAMES[:3], strict=True + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id + ) + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name = {rhel_contenthost.hostname}', + }, + ) + 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() + assert result.succeeded == 1 + target_sat.api.Host(id=target_host.id).remove_ansible_role( + data={'ansible_role_id': role_id} + ) + host_roles = target_host.list_ansible_roles() + assert len(host_roles) == 0 + + @pytest.mark.no_containers + def test_positive_ansible_job_on_multiple_host( + self, + target_sat, + module_org, + rhel9_contenthost, + rhel8_contenthost, + rhel7_contenthost, + module_location, + module_ak_with_synced_repo, ): - assert r1['name'] == r2 - - # Remove roles assigned one by one from HG and nested HG - for role in ROLES[:3]: - target_sat.api.HostGroup(id=hg.id).remove_ansible_role(data={'ansible_role_id': role}) - hg_roles = target_sat.api.HostGroup(id=hg.id).list_ansible_roles() - assert len(hg_roles) == 0 - - for role in ROLES: - target_sat.api.HostGroup(id=hg_nested.id).remove_ansible_role( - data={'ansible_role_id': role} + """Test execution of Ansible job on multiple hosts simultaneously. + + :id: 9369feef-466c-40d3-9d0d-65520d7f21ef + + :customerscenario: true + + :steps: + 1. Register multiple content hosts with satellite + 2. Import a role into satellite + 3. Assign that role to all host + 4. Trigger ansible job keeping all host in a single query + 5. Check the passing and failing of individual hosts + 6. Check if one of the job on a host is failed resulting into whole job is marked as failed. + + :expectedresults: + 1. One of the jobs failing on a single host must impact the overall result as failed. + + :BZ: 2167396, 2190464, 2184117 + """ + hosts = [rhel9_contenthost, rhel8_contenthost, rhel7_contenthost] + SELECTED_ROLE = 'RedHatInsights.insights-client' + for host in hosts: + result = host.register( + module_org, module_location, module_ak_with_synced_repo.name, target_sat + ) + assert result.status == 0, f'Failed to register host: {result.stderr}' + proxy_id = target_sat.nailgun_smart_proxy.id + target_host = host.nailgun_host + target_sat.api.AnsibleRoles().sync( + data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} + ) + role_id = ( + target_sat.api.AnsibleRoles() + .search(query={'search': f'name={SELECTED_ROLE}'})[0] + .id + ) + target_sat.api.Host(id=target_host.id).add_ansible_role( + data={'ansible_role_id': role_id} + ) + host_roles = target_host.list_ansible_roles() + assert host_roles[0]['name'] == SELECTED_ROLE + + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id ) - hg_nested_roles = target_sat.api.HostGroup(id=hg_nested.id).list_ansible_roles() - assert len(hg_nested_roles) == 0 - - -@pytest.mark.rhel_ver_match('[78]') -@pytest.mark.tier2 -def test_positive_read_facts_with_filter( - target_sat, rex_contenthost, filtered_user, rex_host_in_org_and_loc -): - """ - Read host's Ansible facts as a user with a role that has host filter - - :id: 483d5faf-7a4c-4cb7-b14f-369768ad99b0 - - 1. Run Ansible roles on a host - 2. Using API, read Ansible facts of that host - - :expectedresults: Ansible facts returned - - :BZ: 1699188 - - :customerscenario: true - """ - user, password = filtered_user - host = rex_host_in_org_and_loc - - # gather ansible facts by running ansible roles on the host - host.play_ansible_roles() - if is_open('BZ:2216471'): - host_wait = target_sat.api.Host().search( - query={'search': f'name={rex_contenthost.hostname}'} - )[0] - wait_for( - lambda: len(host_wait.get_facts()) > 0, - timeout=30, - delay=2, + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name ^ ({hosts[0].hostname} && {hosts[1].hostname} ' + f'&& {hosts[2].hostname})', + }, ) - - user_cfg = user_nailgun_config(user.login, password) - host = target_sat.api.Host(server_config=user_cfg).search( - query={'search': f'name={rex_contenthost.hostname}'} - )[0] - # get facts through API - facts = host.get_facts() - assert 'subtotal' in facts - assert facts['subtotal'] == 1 - assert 'results' in facts - assert rex_contenthost.hostname in facts['results'] - assert len(facts['results'][rex_contenthost.hostname]) > 0 + target_sat.wait_for_tasks( + f'resource_type = JobInvocation and resource_id = {job["id"]}', + poll_timeout=1000, + must_succeed=False, + ) + result = target_sat.api.JobInvocation(id=job['id']).read() + assert result.succeeded == 2 # SELECTED_ROLE working on rhel8/rhel9 clients + assert result.failed == 1 # SELECTED_ROLE failing on rhel7 client + assert result.status_label == 'failed' diff --git a/tests/foreman/api/test_bookmarks.py b/tests/foreman/api/test_bookmarks.py index b51ac194cb3..5d5833ef563 100644 --- a/tests/foreman/api/test_bookmarks.py +++ b/tests/foreman/api/test_bookmarks.py @@ -17,11 +17,11 @@ import pytest from requests.exceptions import HTTPError -from robottelo.constants import BOOKMARK_ENTITIES +from robottelo.constants import BOOKMARK_ENTITIES_SELECTION from robottelo.utils.datafactory import invalid_values_list, valid_data_list # List of unique bookmark controller values, preserving order -CONTROLLERS = list(dict.fromkeys(entity['controller'] for entity in BOOKMARK_ENTITIES)) +CONTROLLERS = list(dict.fromkeys(entity['controller'] for entity in BOOKMARK_ENTITIES_SELECTION)) @pytest.mark.tier1 diff --git a/tests/foreman/api/test_capsulecontent.py b/tests/foreman/api/test_capsulecontent.py index fe471df8eed..818a808a4c2 100644 --- a/tests/foreman/api/test_capsulecontent.py +++ b/tests/foreman/api/test_capsulecontent.py @@ -1715,3 +1715,55 @@ def test_positive_read_with_non_admin_user( server_config=sc, id=module_capsule_configured.nailgun_capsule.id ).read() assert res.name == module_capsule_configured.hostname, 'External Capsule not found.' + + def test_positive_reclaim_space( + self, + target_sat, + module_capsule_configured, + ): + """Verify the reclaim_space endpoint spawns the Reclaim space task + and apidoc references the endpoint correctly. + + :id: eb16ed53-0489-4bb9-a0da-8d857a1c7d06 + + :setup: + 1. A registered external Capsule. + + :steps: + 1. Trigger the reclaim space task via API, check it succeeds. + 2. Check the apidoc references the correct endpoint. + + :expectedresults: + 1. Reclaim_space endpoint spawns the Reclaim space task and it succeeds. + 2. Apidoc references the correct endpoint. + + :CaseImportance: Medium + + :BZ: 2218179 + + :customerscenario: true + """ + # Trigger the reclaim space task via API, check it succeeds + task = module_capsule_configured.nailgun_capsule.content_reclaim_space() + assert task, 'No task was created for reclaim space.' + assert ( + 'Actions::Pulp3::CapsuleContent::ReclaimSpace' in task['label'] + ), 'Unexpected task triggered' + assert 'success' in task['result'], 'Reclaim task did not succeed' + + # Check the apidoc references the correct endpoint + try: + reclaim_doc = next( + method + for method in target_sat.apidoc['docs']['resources']['capsule_content']['methods'] + if '/apidoc/v2/capsule_content/reclaim_space' in method['doc_url'] + ) + except StopIteration: + raise AssertionError( + 'Could not find the reclaim_space apidoc at the expected path.' + ) from None + assert len(reclaim_doc['apis']) == 1 + assert reclaim_doc['apis'][0]['http_method'] == 'POST', 'POST method was expected.' + assert ( + reclaim_doc['apis'][0]['api_url'] == '/katello/api/capsules/:id/content/reclaim_space' + ), 'Documented path did not meet the expectation.' diff --git a/tests/foreman/api/test_errata.py b/tests/foreman/api/test_errata.py index da257c8d983..81974401a9f 100644 --- a/tests/foreman/api/test_errata.py +++ b/tests/foreman/api/test_errata.py @@ -1353,7 +1353,6 @@ def _set_prerequisites_for_swid_repos(vm): 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(r'subscription-manager repos --enable \*', 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) @@ -1365,37 +1364,47 @@ def _validate_swid_tags_installed(vm, module_name): 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.pit_client -@pytest.mark.parametrize( - 'function_repos_collection_with_manifest', - [{'YumRepository': {'url': settings.repos.swid_tag.url, 'distro': 'rhel8'}}], - indirect=True, -) @pytest.mark.no_containers +@pytest.mark.rhel_ver_match('8') def test_errata_installation_with_swidtags( - function_repos_collection_with_manifest, rhel8_contenthost, target_sat + 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. create product and repository having swid tags - 2. create content view and published it with repository - 3. create activation key and register content host - 4. create rhel8, swid repos on content host - 5. install swid-tools, dnf-plugin-swidtags packages on content host - 6. install older module stream and generate errata, swid tag - 7. assert errata count, swid tags are generated - 8. install errata vis updating module stream - 9. assert errata count and swid tag after module update + 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 + :expectedresults: + swid tags should get updated after errata installation via module stream update :CaseAutomation: Automated @@ -1406,44 +1415,100 @@ def test_errata_installation_with_swidtags( """ module_name = 'kangaroo' version = '20180704111719' - # setup base_rhel8 and sat_tools repos - rhel8_contenthost.create_custom_repos( - **{ - 'baseos': settings.repos.rhel8_os.baseos, - 'appstream': settings.repos.rhel8_os.appstream, - } - ) - function_repos_collection_with_manifest.setup_virtual_machine(rhel8_contenthost) + org = module_sca_manifest_org + lce = errata_host_lce + cv = target_sat.api.ContentView( + organization=org, + environment=[lce], + ).create() - # install older module stream - rhel8_contenthost.add_rex_key(satellite=target_sat) - _set_prerequisites_for_swid_repos(rhel8_contenthost) - _run_remote_command_on_content_host( - f'dnf -y module install {module_name}:0:{version}', rhel8_contenthost + 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, ) - target_sat.cli.Host.errata_recalculate({'host-id': rhel8_contenthost.nailgun_host.id}) + 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 - before_errata_apply_result = _run_remote_command_on_content_host( - f"swidq -i -n {module_name} | grep 'File' | grep -o 'rpm-.*.swidtag'", - rhel8_contenthost, - return_result=True, + 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.' ) - assert before_errata_apply_result != '' - assert (applicable_errata_count := rhel8_contenthost.applicable_errata_count) == 1 # apply modular errata - _run_remote_command_on_content_host(f'dnf -y module update {module_name}', rhel8_contenthost) - _run_remote_command_on_content_host('dnf -y upload-profile', rhel8_contenthost) - target_sat.cli.Host.errata_recalculate({'host-id': rhel8_contenthost.nailgun_host.id}) - rhel8_contenthost.execute('subscription-manager repos') - applicable_errata_count -= 1 - assert rhel8_contenthost.applicable_errata_count == applicable_errata_count - after_errata_apply_result = _run_remote_command_on_content_host( - f"swidq -i -n {module_name} | grep 'File'| grep -o 'rpm-.*.swidtag'", - rhel8_contenthost, - return_result=True, + 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"', ) - # swidtags get updated based on package version + 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 diff --git a/tests/foreman/api/test_host.py b/tests/foreman/api/test_host.py index 94730d56925..eb3e89dc6aa 100644 --- a/tests/foreman/api/test_host.py +++ b/tests/foreman/api/test_host.py @@ -261,10 +261,10 @@ def test_positive_create_inherit_lce_cv( ).create() host = module_target_sat.api.Host(hostgroup=hostgroup, organization=module_org).create() assert ( - host.content_facet_attributes['lifecycle_environment_id'] + host.content_facet_attributes['lifecycle_environment']['id'] == hostgroup.lifecycle_environment.id ) - assert host.content_facet_attributes['content_view_id'] == hostgroup.content_view.id + assert host.content_facet_attributes['content_view']['id'] == hostgroup.content_view.id @pytest.mark.tier2 @@ -625,16 +625,16 @@ def test_positive_create_and_update_with_content_view( 'lifecycle_environment_id': module_lce_library.id, }, ).create() - assert host.content_facet_attributes['content_view_id'] == module_default_org_view.id - assert host.content_facet_attributes['lifecycle_environment_id'] == module_lce_library.id + assert host.content_facet_attributes['content_view']['id'] == module_default_org_view.id + assert host.content_facet_attributes['lifecycle_environment']['id'] == module_lce_library.id host.content_facet_attributes = { 'content_view_id': module_default_org_view.id, 'lifecycle_environment_id': module_lce_library.id, } host = host.update(['content_facet_attributes']) - assert host.content_facet_attributes['content_view_id'] == module_default_org_view.id - assert host.content_facet_attributes['lifecycle_environment_id'] == module_lce_library.id + assert host.content_facet_attributes['content_view']['id'] == module_default_org_view.id + assert host.content_facet_attributes['lifecycle_environment']['id'] == module_lce_library.id @pytest.mark.tier1 diff --git a/tests/foreman/api/test_notifications.py b/tests/foreman/api/test_notifications.py index 5ed5c940678..4a43be0084e 100644 --- a/tests/foreman/api/test_notifications.py +++ b/tests/foreman/api/test_notifications.py @@ -333,3 +333,32 @@ def test_positive_notification_failed_repo_sync(failed_repo_sync_task, root_mail body_text = mime_body.as_string() assert product_name in body_text assert f'/foreman_tasks/tasks/{task_id}' in body_text + + +@pytest.mark.tier1 +def test_positive_notification_recipients(target_sat): + """Check that endpoint `/notification_recipients` works and returns correct data structure. + + :id: 10e0fac2-f11f-11ee-ba60-000c2989e153 + + :steps: + 1. Do a GET request to /notification_recipients endpoint. + 2. Check the returned data structure for expected keys. + + :BZ: 2249970 + + :customerscenario: true + """ + notification_keys = [ + 'id', + 'seen', + 'level', + 'text', + 'created_at', + 'group', + 'actions', + ] + + recipients = target_sat.api.NotificationRecipients().read() + for notification in recipients.notifications: + assert set(notification_keys) == set(notification.keys()) diff --git a/tests/foreman/api/test_repositories.py b/tests/foreman/api/test_repositories.py index 9c577dd7349..68dc13b0657 100644 --- a/tests/foreman/api/test_repositories.py +++ b/tests/foreman/api/test_repositories.py @@ -18,7 +18,12 @@ from robottelo import constants from robottelo.config import settings -from robottelo.constants import DEFAULT_ARCHITECTURE, MIRRORING_POLICIES, REPOS +from robottelo.constants import ( + DEFAULT_ARCHITECTURE, + MIRRORING_POLICIES, + REPOS, +) +from robottelo.constants.repos import FAKE_ZST_REPO from robottelo.exceptions import CLIReturnCodeError from robottelo.utils.datafactory import parametrized @@ -186,6 +191,39 @@ def test_positive_sync_kickstart_repo(module_sca_manifest_org, target_sat): assert rh_repo.content_counts['rpm'] > 0 +def test_positive_sync_upstream_repo_with_zst_compression( + module_org, module_product, module_target_sat +): + """Sync upstream repo having zst compression and verify it succeeds. + + :id: 1eddff2a-b6b5-420b-a0e8-ba6a05c11ca4 + + :expectedresults: Repo sync is successful and no zst type compression errors are present in /var/log/messages. + + :steps: + + 1. Sync upstream repository having zst type compression. + 2. Assert that no errors related to compression type are present in + /var/log/messages. + 3. Assert that sync was executed properly. + + :BZ: 2241934 + + :customerscenario: true + """ + repo = module_target_sat.api.Repository( + product=module_product, content_type='yum', url=FAKE_ZST_REPO + ).create() + assert repo.read().content_counts['rpm'] == 0 + sync = module_product.sync() + assert sync['result'] == 'success' + assert repo.read().content_counts['rpm'] > 0 + result = module_target_sat.execute( + 'grep pulp /var/log/messages | grep "Cannot detect compression type"' + ) + assert result.status == 1 + + @pytest.mark.tier1 def test_negative_upload_expired_manifest(module_org, target_sat): """Upload an expired manifest and attempt to refresh it diff --git a/tests/foreman/api/test_repository.py b/tests/foreman/api/test_repository.py index 728249f097c..f42c33f86c7 100644 --- a/tests/foreman/api/test_repository.py +++ b/tests/foreman/api/test_repository.py @@ -485,14 +485,13 @@ def test_negative_update_to_invalid_download_policy(self, repo, target_sat): @pytest.mark.tier1 @pytest.mark.parametrize( 'repo_options', - **datafactory.parametrized( - [ - {'content_type': content_type, 'download_policy': 'on_demand'} - for content_type in constants.REPO_TYPE - if content_type != 'yum' - ] - ), + [ + {'content_type': content_type, 'download_policy': 'on_demand'} + for content_type in constants.REPO_TYPE + if content_type not in ['yum', 'docker'] + ], indirect=True, + ids=lambda x: x['content_type'], ) def test_negative_create_non_yum_with_download_policy(self, repo_options, target_sat): """Verify that non-YUM repositories cannot be created with diff --git a/tests/foreman/api/test_rhc.py b/tests/foreman/api/test_rhc.py index 30559fe406e..391277bfb38 100644 --- a/tests/foreman/api/test_rhc.py +++ b/tests/foreman/api/test_rhc.py @@ -6,7 +6,7 @@ :CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/api/test_rhcloud_inventory.py b/tests/foreman/api/test_rhcloud_inventory.py index 76a33688f90..2eb17f408f3 100644 --- a/tests/foreman/api/test_rhcloud_inventory.py +++ b/tests/foreman/api/test_rhcloud_inventory.py @@ -6,7 +6,7 @@ :CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/api/test_subnet.py b/tests/foreman/api/test_subnet.py index b4882116f8e..99873b537f5 100644 --- a/tests/foreman/api/test_subnet.py +++ b/tests/foreman/api/test_subnet.py @@ -336,9 +336,8 @@ def test_negative_update_parameter(new_name, target_sat): sub_param.update(['name']) -@pytest.mark.stubbed @pytest.mark.tier2 -def test_positive_update_subnet_parameter_host_impact(): +def test_positive_update_subnet_parameter_host_impact(target_sat): """Update in parameter name and value from subnet component updates the parameter in host inheriting that subnet @@ -353,12 +352,29 @@ def test_positive_update_subnet_parameter_host_impact(): :expectedresults: 1. The inherited subnet parameter in host should have - updated name and value - 2. The inherited subnet parameter in host enc should have - updated name and value + updated name and value. :BZ: 1470014 """ + parameter = [{'name': gen_string('alpha'), 'value': gen_string('alpha')}] + org = target_sat.api.Organization().create() + loc = target_sat.api.Location(organization=[org]).create() + org_subnet = target_sat.api.Subnet( + location=[loc], organization=[org], subnet_parameters_attributes=parameter + ).create() + assert parameter[0]['name'] == org_subnet.subnet_parameters_attributes[0]['name'] + assert parameter[0]['value'] == org_subnet.subnet_parameters_attributes[0]['value'] + host = target_sat.api.Host(location=loc, organization=org, subnet=org_subnet).create() + parameter_new_value = [{'name': gen_string('alpha'), 'value': gen_string('alpha')}] + org_subnet.subnet_parameters_attributes = parameter_new_value + org_subnet.update(['subnet_parameters_attributes']) + assert ( + host.subnet.read().subnet_parameters_attributes[0]['name'] == parameter_new_value[0]['name'] + ) + assert ( + host.subnet.read().subnet_parameters_attributes[0]['value'] + == parameter_new_value[0]['value'] + ) @pytest.mark.tier1 diff --git a/tests/foreman/cli/test_ansible.py b/tests/foreman/cli/test_ansible.py index 8cffe15b20d..707acbcfec9 100644 --- a/tests/foreman/cli/test_ansible.py +++ b/tests/foreman/cli/test_ansible.py @@ -4,156 +4,520 @@ :CaseAutomation: Automated -:CaseComponent: Ansible-ConfigurationManagement - :Team: Rocket :CaseImportance: High - """ +from time import sleep + from fauxfactory import gen_string import pytest from robottelo.config import settings -@pytest.mark.e2e -@pytest.mark.no_containers -@pytest.mark.rhel_ver_match('[^6].*') -def test_positive_ansible_e2e(target_sat, module_org, rhel_contenthost): +def assert_job_invocation_result( + sat, invocation_command_id, client_hostname, expected_result='success' +): + """Asserts the job invocation finished with the expected result and fetches job output + when error occurs. Result is one of: success, pending, error, warning""" + result = sat.cli.JobInvocation.info({'id': invocation_command_id}) + try: + assert result[expected_result] == '1' + except AssertionError as err: + raise AssertionError( + 'host output: {}'.format( + ' '.join( + sat.cli.JobInvocation.get_output( + {'id': invocation_command_id, 'host': client_hostname} + ) + ) + ) + ) from err + + +@pytest.mark.upgrade +class TestAnsibleCfgMgmt: + """Test class for Configuration Management with Ansible + + :CaseComponent: Ansible-ConfigurationManagement """ - Test successful execution of Ansible Job on host. - :id: 0c52bc63-a41a-4f48-a980-fe49b4ecdbdc + @pytest.mark.e2e + @pytest.mark.no_containers + @pytest.mark.rhel_ver_match('[^6].*') + def test_positive_ansible_e2e( + self, target_sat, module_sca_manifest_org, module_ak_with_cv, rhel_contenthost + ): + """ + Test successful execution of Ansible Job on host. - :steps: - 1. Register a content host with satellite - 2. Import a role into satellite - 3. Assign that role to a host - 4. Assert that the role and variable were assigned to the host successfully - 5. Run the Ansible playbook associated with that role - 6. Check if the job is executed successfully. - 7. Disassociate the Role from the host. - 8. Delete the assigned ansible role + :id: 0c52bc63-a41a-4f48-a980-fe49b4ecdbdc - :expectedresults: - 1. Host should be assigned the proper role. - 2. Job execution must be successful. - 3. Operations performed with hammer must be successful. + :steps: + 1. Register a content host with satellite + 2. Import a role into satellite + 3. Assign that role to a host + 4. Assert that the role and variable were assigned to the host successfully + 5. Run the Ansible playbook associated with that role + 6. Check if the job is executed successfully. + 7. Disassociate the Role from the host. + 8. Delete the assigned ansible role - :BZ: 2154184 + :expectedresults: + 1. Host should be assigned the proper role. + 2. Job execution must be successful. + 3. Operations performed with hammer must be successful. - :customerscenario: true + :BZ: 2154184 - :CaseImportance: Critical - """ - SELECTED_ROLE = 'RedHatInsights.insights-client' - SELECTED_ROLE_1 = 'theforeman.foreman_scap_client' - SELECTED_VAR = gen_string('alpha') - # disable batch tasks to test BZ#2154184 - target_sat.cli.Settings.set({'name': 'foreman_tasks_proxy_batch_trigger', 'value': 'false'}) - if rhel_contenthost.os_version.major <= 7: - rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) - assert rhel_contenthost.execute('yum install -y insights-client').status == 0 - rhel_contenthost.install_katello_ca(target_sat) - rhel_contenthost.register_contenthost(module_org.label, force=True) - assert rhel_contenthost.subscribed - rhel_contenthost.add_rex_key(satellite=target_sat) - proxy_id = target_sat.nailgun_smart_proxy.id - target_host = rhel_contenthost.nailgun_host - - target_sat.cli.Ansible.roles_sync( - {'role-names': f'{SELECTED_ROLE},{SELECTED_ROLE_1}', 'proxy-id': proxy_id} - ) + :customerscenario: true + """ + SELECTED_ROLE = 'RedHatInsights.insights-client' + SELECTED_ROLE_1 = 'theforeman.foreman_scap_client' + SELECTED_VAR = gen_string('alpha') + proxy_id = target_sat.nailgun_smart_proxy.id + # disable batch tasks to test BZ#2154184 + target_sat.cli.Settings.set({'name': 'foreman_tasks_proxy_batch_trigger', 'value': 'false'}) + result = rhel_contenthost.register( + module_sca_manifest_org, None, module_ak_with_cv.name, target_sat + ) + assert result.status == 0, f'Failed to register host: {result.stderr}' + if rhel_contenthost.os_version.major <= 7: + rhel_contenthost.create_custom_repos(rhel7=settings.repos.rhel7_os) + assert rhel_contenthost.execute('yum install -y insights-client').status == 0 + target_host = rhel_contenthost.nailgun_host - result = target_sat.cli.Host.ansible_roles_add( - {'id': target_host.id, 'ansible-role': SELECTED_ROLE} - ) - assert 'Ansible role has been associated.' in result[0]['message'] + target_sat.cli.Ansible.roles_sync( + {'role-names': f'{SELECTED_ROLE},{SELECTED_ROLE_1}', 'proxy-id': proxy_id} + ) + result = target_sat.cli.Host.ansible_roles_add( + {'id': target_host.id, 'ansible-role': SELECTED_ROLE} + ) + assert 'Ansible role has been associated.' in result[0]['message'] - target_sat.cli.Ansible.variables_create( - {'variable': SELECTED_VAR, 'ansible-role': SELECTED_ROLE} - ) + target_sat.cli.Ansible.variables_create( + {'variable': SELECTED_VAR, 'ansible-role': SELECTED_ROLE} + ) - assert SELECTED_ROLE, ( - SELECTED_VAR in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout - ) - template_id = ( - target_sat.api.JobTemplate() - .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] - .id - ) - job = target_sat.api.JobInvocation().run( - synchronous=False, - data={ - 'job_template_id': template_id, - 'targeting_type': 'static_query', - 'search_query': f'name = {rhel_contenthost.hostname}', - }, - ) - 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() - assert result.succeeded == 1 + assert SELECTED_ROLE, ( + SELECTED_VAR in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout + ) + template_id = ( + target_sat.api.JobTemplate() + .search(query={'search': 'name="Ansible Roles - Ansible Default"'})[0] + .id + ) + job = target_sat.api.JobInvocation().run( + synchronous=False, + data={ + 'job_template_id': template_id, + 'targeting_type': 'static_query', + 'search_query': f'name = {rhel_contenthost.hostname}', + }, + ) + 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() + assert result.succeeded == 1 - result = target_sat.cli.Host.ansible_roles_assign( - {'id': target_host.id, 'ansible-roles': f'{SELECTED_ROLE},{SELECTED_ROLE_1}'} - ) - assert 'Ansible roles were assigned to the host' in result[0]['message'] + result = target_sat.cli.Host.ansible_roles_assign( + {'id': target_host.id, 'ansible-roles': f'{SELECTED_ROLE},{SELECTED_ROLE_1}'} + ) + assert 'Ansible roles were assigned to the host' in result[0]['message'] - result = target_sat.cli.Host.ansible_roles_remove( - {'id': target_host.id, 'ansible-role': SELECTED_ROLE} - ) - assert 'Ansible role has been disassociated.' in result[0]['message'] + result = target_sat.cli.Host.ansible_roles_remove( + {'id': target_host.id, 'ansible-role': SELECTED_ROLE} + ) + assert 'Ansible role has been disassociated.' in result[0]['message'] - result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) - assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] + result = target_sat.cli.Ansible.roles_delete({'name': SELECTED_ROLE}) + assert f'Ansible role [{SELECTED_ROLE}] was deleted.' in result[0]['message'] - assert SELECTED_ROLE, ( - SELECTED_VAR not in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout - ) + assert SELECTED_ROLE, ( + SELECTED_VAR not in target_sat.cli.Ansible.variables_info({'name': SELECTED_VAR}).stdout + ) + @pytest.mark.e2e + @pytest.mark.tier2 + def test_add_and_remove_ansible_role_hostgroup(self, target_sat): + """ + Test add and remove functionality for ansible roles in hostgroup via CLI -@pytest.mark.e2e -@pytest.mark.tier2 -def test_add_and_remove_ansible_role_hostgroup(target_sat): - """ - Test add and remove functionality for ansible roles in hostgroup via CLI + :id: 2c6fda14-4cd2-490a-b7ef-7a08f8164fad + + :customerscenario: true + + :steps: + 1. Create a hostgroup + 2. Sync few ansible roles + 3. Assign a few ansible roles with the host group + 4. Add some ansible role with the host group + 5. Remove the added ansible roles from the host group - :id: 2c6fda14-4cd2-490a-b7ef-7a08f8164fad + :expectedresults: + 1. Ansible role assign/add/remove functionality should work as expected in CLI - :customerscenario: true + :BZ: 2029402 + """ + ROLES = [ + 'theforeman.foreman_scap_client', + 'redhat.satellite.hostgroups', + 'RedHatInsights.insights-client', + ] + proxy_id = target_sat.nailgun_smart_proxy.id + hg_name = gen_string('alpha') + result = target_sat.cli.HostGroup.create({'name': hg_name}) + assert result['name'] == hg_name + target_sat.cli.Ansible.roles_sync({'role-names': ROLES, 'proxy-id': proxy_id}) + result = target_sat.cli.HostGroup.ansible_roles_assign( + {'name': hg_name, 'ansible-roles': f'{ROLES[1]},{ROLES[2]}'} + ) + assert 'Ansible roles were assigned to the hostgroup' in result[0]['message'] + result = target_sat.cli.HostGroup.ansible_roles_add( + {'name': hg_name, 'ansible-role': ROLES[0]} + ) + assert 'Ansible role has been associated.' in result[0]['message'] + result = target_sat.cli.HostGroup.ansible_roles_remove( + {'name': hg_name, 'ansible-role': ROLES[0]} + ) + assert 'Ansible role has been disassociated.' in result[0]['message'] - :steps: - 1. Create a hostgroup - 2. Sync few ansible roles - 3. Assign a few ansible roles with the host group - 4. Add some ansible role with the host group - 5. Remove the added ansible roles from the host group - :expectedresults: - 1. Ansible role assign/add/remove functionality should work as expected in CLI +@pytest.mark.tier3 +@pytest.mark.upgrade +class TestAnsibleREX: + """Test class for remote execution via Ansible - :BZ: 2029402 + :CaseComponent: Ansible-RemoteExecution """ - ROLES = [ - 'theforeman.foreman_scap_client', - 'redhat.satellite.hostgroups', - 'RedHatInsights.insights-client', - ] - proxy_id = target_sat.nailgun_smart_proxy.id - hg_name = gen_string('alpha') - result = target_sat.cli.HostGroup.create({'name': hg_name}) - assert result['name'] == hg_name - target_sat.cli.Ansible.roles_sync({'role-names': ROLES, 'proxy-id': proxy_id}) - result = target_sat.cli.HostGroup.ansible_roles_assign( - {'name': hg_name, 'ansible-roles': f'{ROLES[1]},{ROLES[2]}'} - ) - assert 'Ansible roles were assigned to the hostgroup' in result[0]['message'] - result = target_sat.cli.HostGroup.ansible_roles_add({'name': hg_name, 'ansible-role': ROLES[0]}) - assert 'Ansible role has been associated.' in result[0]['message'] - result = target_sat.cli.HostGroup.ansible_roles_remove( - {'name': hg_name, 'ansible-role': ROLES[0]} + + @pytest.mark.pit_client + @pytest.mark.pit_server + @pytest.mark.rhel_ver_match('[^6]') + def test_positive_run_effective_user_job(self, rex_contenthost, target_sat): + """Tests Ansible REX job having effective user runs successfully + + :id: a5fa20d8-c2bd-4bbf-a6dc-bf307b59dd8c + + :steps: + 0. Create a VM and register to SAT and prepare for REX (ssh key) + 1. Run Ansible Command job for the host to create a user + 2. Run Ansible Command job using effective user + 3. Check the job result at the host is done under that user + + :expectedresults: multiple asserts along the code + + :parametrized: yes + """ + client = rex_contenthost + # create a user on client via remote job + username = gen_string('alpha') + filename = gen_string('alpha') + make_user_job = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': f'command=useradd -m {username}', + 'search-query': f'name ~ {client.hostname}', + } + ) + assert_job_invocation_result(target_sat, make_user_job['id'], client.hostname) + # create a file as new user + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': f'command=touch /home/{username}/{filename}', + 'search-query': f'name ~ {client.hostname}', + 'effective-user': username, + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + + # check the file owner + result = client.execute( + f'''stat -c '%U' /home/{username}/{filename}''', + ) + # assert the file is owned by the effective user + assert username == result.stdout.strip('\n'), 'file ownership mismatch' + + @pytest.mark.rhel_ver_list([8]) + def test_positive_run_reccuring_job(self, rex_contenthost, target_sat): + """Tests Ansible REX reccuring job runs successfully multiple times + + :id: 49b0d31d-58f9-47f1-aa5d-561a1dcb0d66 + + :setup: + 1. Create a VM, register to SAT and configure REX (ssh-key) + + :steps: + 1. Run recurring Ansible Command job for the host + 2. Check the multiple job results at the host + + :expectedresults: multiple asserts along the code + + :bz: 2129432 + + :customerscenario: true + + :parametrized: yes + """ + client = rex_contenthost + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': 'command=ls', + 'search-query': f'name ~ {client.hostname}', + 'cron-line': '* * * * *', # every minute + 'max-iteration': 2, # just two runs + } + ) + result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) + sleep(150) + rec_logic = target_sat.cli.RecurringLogic.info({'id': result['recurring-logic-id']}) + assert rec_logic['state'] == 'finished' + assert rec_logic['iteration'] == '2' + # 2129432 + rec_logic_keys = rec_logic.keys() + assert 'action' in rec_logic_keys + assert 'last-occurrence' in rec_logic_keys + assert 'next-occurrence' in rec_logic_keys + assert 'state' in rec_logic_keys + assert 'purpose' in rec_logic_keys + assert 'iteration' in rec_logic_keys + assert 'iteration-limit' in rec_logic_keys + + @pytest.mark.rhel_ver_list([8]) + def test_positive_run_concurrent_jobs(self, rex_contenthosts, target_sat): + """Tests Ansible REX concurent jobs without batch trigger + + :id: ad0f108c-03f2-49c7-8732-b1056570567b + + :steps: + 1. Create 2 hosts, disable foreman_tasks_proxy_batch_trigger + 2. Run Ansible Command job with concurrency-setting + + :expectedresults: multiple asserts along the code + + :BZ: 1817320 + + :customerscenario: true + + :parametrized: yes + """ + clients = rex_contenthosts + param_name = 'foreman_tasks_proxy_batch_trigger' + target_sat.cli.GlobalParameter().set({'name': param_name, 'value': 'false'}) + output_msgs = [] + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Run Command - Ansible Default', + 'inputs': 'command=ls', + 'search-query': f'name ~ {clients[0].hostname} or name ~ {clients[1].hostname}', + 'concurrency-level': 2, + } + ) + for vm in clients: + output_msgs.append( + 'host output from {}: {}'.format( + vm.hostname, + ' '.join( + target_sat.cli.JobInvocation.get_output( + {'id': invocation_command['id'], 'host': vm.hostname} + ) + ), + ) + ) + result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) + assert result['success'] == '2', output_msgs + target_sat.cli.GlobalParameter().delete({'name': param_name}) + assert len(target_sat.cli.GlobalParameter().list({'search': param_name})) == 0 + + @pytest.mark.rhel_ver_list([8]) + def test_positive_run_serial(self, rex_contenthosts, target_sat): + """Tests subtasks in a job run one by one when concurrency level set to 1 + + :id: 5ce39447-82d0-42df-81be-16ed3d67a2a4 + + :setup: + 1. Create 2 hosts, register to SAT and configure REX (ssh-key) + + :steps: + 1. Run a bash command job with concurrency level 1 + + :expectedresults: First subtask should run immediately, second one after the first one finishes + + :parametrized: yes + """ + hosts = rex_contenthosts + output_msgs = [] + template_file = f'/root/{gen_string("alpha")}.template' + target_sat.execute( + f"echo 'rm /root/test-<%= @host %>; echo $(date +%s) >> /root/test-<%= @host %>; sleep 120; echo $(date +%s) >> /root/test-<%= @host %>' > {template_file}" + ) + template = target_sat.cli.JobTemplate.create( + { + 'name': gen_string('alpha'), + 'file': template_file, + 'job-category': 'Commands', + 'provider-type': 'script', + } + ) + invocation = target_sat.cli_factory.job_invocation( + { + 'job-template': template['name'], + 'search-query': f'name ~ {hosts[0].hostname} or name ~ {hosts[1].hostname}', + 'concurrency-level': 1, + } + ) + for vm in hosts: + output_msgs.append( + 'host output from {}: {}'.format( + vm.hostname, + ' '.join( + target_sat.cli.JobInvocation.get_output( + {'id': invocation['id'], 'host': vm.hostname} + ) + ), + ) + ) + result = target_sat.cli.JobInvocation.info({'id': invocation['id']}) + assert result['success'] == '2', output_msgs + # assert for time diffs + file1 = hosts[0].execute('cat /root/test-$(hostname)').stdout + file2 = hosts[1].execute('cat /root/test-$(hostname)').stdout + file1_start, file1_end = map(int, file1.rstrip().split('\n')) + file2_start, file2_end = map(int, file2.rstrip().split('\n')) + if file1_start > file2_start: + file1_start, file1_end, file2_start, file2_end = ( + file2_start, + file2_end, + file1_start, + file1_end, + ) + assert file1_end - file1_start >= 120 + assert file2_end - file2_start >= 120 + assert file2_start >= file1_end # the jobs did NOT run concurrently + + @pytest.mark.e2e + @pytest.mark.no_containers + @pytest.mark.pit_server + @pytest.mark.rhel_ver_match('[^6].*') + @pytest.mark.skipif( + (not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url' ) - assert 'Ansible role has been disassociated.' in result[0]['message'] + def test_positive_run_packages_and_services_job( + self, rhel_contenthost, module_sca_manifest_org, module_ak_with_cv, target_sat + ): + """Tests Ansible REX job can install packages and start services + + :id: 47ed82fb-77ca-43d6-a52e-f62bae5d3a42 + + :setup: + 1. Create a VM, register to SAT and configure REX (ssh-key) + + :steps: + 1. Run Ansible Package job for the host to install a package + 2. Check the package is present at the host + 3. Run Ansible Service job for the host to start a service + 4. Check the service is started on the host + + :expectedresults: multiple asserts along the code + + :bz: 1872688, 1811166 + + :customerscenario: true + + :parametrized: yes + """ + client = rhel_contenthost + packages = ['tapir'] + result = client.register( + module_sca_manifest_org, + None, + module_ak_with_cv.name, + target_sat, + repo=settings.repos.yum_3.url, + ) + assert result.status == 0, f'Failed to register host: {result.stderr}' + # install package + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Package Action - Ansible Default', + 'inputs': 'state=latest, name={}'.format(*packages), + 'search-query': f'name ~ {client.hostname}', + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + result = client.run(f'rpm -q {" ".join(packages)}') + assert result.status == 0 + + # stop a service + service = 'rsyslog' + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Service Action - Ansible Default', + 'inputs': f'state=stopped, name={service}', + 'search-query': f"name ~ {client.hostname}", + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + result = client.execute(f'systemctl status {service}') + assert result.status == 3 + + # start it again + invocation_command = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Service Action - Ansible Default', + 'inputs': f'state=started, name={service}', + 'search-query': f'name ~ {client.hostname}', + } + ) + assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) + result = client.execute(f'systemctl status {service}') + assert result.status == 0 + + @pytest.mark.rhel_ver_list([8]) + def test_positive_install_ansible_collection(self, rex_contenthost, target_sat): + """Test whether Ansible collection can be installed via Ansible REX + + :id: ad25aee5-4ea3-4743-a301-1c6271856f79 + + :steps: + 1. Upload a manifest. + 2. Register content host to Satellite with REX setup + 3. Enable Ansible repo on content host. + 4. Install ansible or ansible-core package + 5. Run REX job to install Ansible collection on content host. + + :expectedresults: Ansible collection can be installed on content host via REX. + """ + client = rex_contenthost + # Enable Ansible repository and Install ansible or ansible-core package + client.create_custom_repos(rhel8_aps=settings.repos.rhel8_os.appstream) + assert client.execute('dnf -y install ansible-core').status == 0 + + collection_job = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Ansible Collection - Install from Galaxy', + 'inputs': 'ansible_collections_list="oasis_roles.system"', + 'search-query': f'name ~ {client.hostname}', + } + ) + result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) + assert result['success'] == '1' + collection_path = client.execute('ls /etc/ansible/collections/ansible_collections').stdout + assert 'oasis_roles' in collection_path + + # Extend test with custom collections_path advanced input field + collection_job = target_sat.cli_factory.job_invocation( + { + 'job-template': 'Ansible Collection - Install from Galaxy', + 'inputs': 'ansible_collections_list="oasis_roles.system", collections_path="~/"', + 'search-query': f'name ~ {client.hostname}', + } + ) + result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) + assert result['success'] == '1' + collection_path = client.execute('ls ~/ansible_collections').stdout + assert 'oasis_roles' in collection_path diff --git a/tests/foreman/cli/test_errata.py b/tests/foreman/cli/test_errata.py index 4af3d9be2d3..0dc3e091654 100644 --- a/tests/foreman/cli/test_errata.py +++ b/tests/foreman/cli/test_errata.py @@ -12,6 +12,7 @@ """ from datetime import date, datetime, timedelta from operator import itemgetter +import re from broker import Broker import pytest @@ -1534,3 +1535,44 @@ def test_errata_list_by_contentview_filter(module_sca_manifest_org, module_targe ) ) assert errata_count != errata_count_cvf + + +@pytest.mark.rhel_ver_match('8') +def test_positive_verify_errata_recalculate_tasks(target_sat, errata_host): + """Verify 'Actions::Katello::Applicability::Hosts::BulkGenerate' tasks proceed on 'worker-hosts-queue-1.service' + + :id: d5f89df2-b8fb-4aec-839d-b548b0aadc0c + + :setup: Register host which has applicable errata with satellite + + :steps: + 1. Run 'hammer host errata recalculate --host-id NUMBER' (will trigger inside errata_host fixture) + 2. Check systemctl or journalctl or /var/log/messages for 'dynflow-sidekiq@worker-hosts-queue-1.service' + + :expectedresults: worker-hosts-queue-1 should proceed with task and this can be cross verify using unique PID + + :customerscenario: true + + :BZ: 2249736 + """ + # Recalculate errata command has triggered inside errata_host fixture + + # get PID of 'worker-hosts-queue-1' from /var/log/messages + message_log = target_sat.execute( + 'grep "dynflow-sidekiq@worker-hosts-queue-1" /var/log/messages | tail -1' + ) + assert message_log.status == 0 + pattern_1 = r'\[(.*?)\]' # pattern to capture PID of 'worker-hosts-queue-1' + match = re.search(pattern_1, message_log.stdout) + pid_log_message = match.group(1) + + # get PID of 'worker-hosts-queue-1' from systemctl command output + systemctl_log = target_sat.execute( + 'systemctl status dynflow-sidekiq@worker-hosts-queue-1.service | grep -i "Main PID:"' + ) + assert systemctl_log.status == 0 + pattern_2 = r'\: (.*?) \(' # pattern to capture PID of 'worker-hosts-queue-1' + match = re.search(pattern_2, systemctl_log.stdout) + pid_systemctl_cmd = match.group(1) + + assert pid_log_message == pid_systemctl_cmd diff --git a/tests/foreman/cli/test_remoteexecution.py b/tests/foreman/cli/test_remoteexecution.py index d892b4dbb4a..67231123ec1 100644 --- a/tests/foreman/cli/test_remoteexecution.py +++ b/tests/foreman/cli/test_remoteexecution.py @@ -16,7 +16,6 @@ import random from time import sleep -from broker import Broker from dateutil.relativedelta import FR, relativedelta from fauxfactory import gen_string import pytest @@ -24,8 +23,7 @@ from robottelo import constants from robottelo.config import settings -from robottelo.constants import FAKE_4_CUSTOM_PACKAGE, PRDS, REPOS, REPOSET -from robottelo.hosts import ContentHost +from robottelo.constants import FAKE_4_CUSTOM_PACKAGE from robottelo.utils import ohsnap from robottelo.utils.datafactory import filtered_datapoint, parametrized @@ -44,24 +42,6 @@ def valid_feature_names(): ] -@pytest.fixture -def fixture_sca_vmsetup(request, module_sca_manifest_org, target_sat): - """Create VM and register content host to Simple Content Access organization""" - if '_count' in request.param: - with Broker( - nick=request.param['nick'], - host_class=ContentHost, - _count=request.param['_count'], - ) as clients: - for client in clients: - client.configure_rex(satellite=target_sat, org=module_sca_manifest_org) - yield clients - else: - with Broker(nick=request.param['nick'], host_class=ContentHost) as client: - client.configure_rex(satellite=target_sat, org=module_sca_manifest_org) - yield client - - @pytest.fixture def infra_host(request, target_sat, module_capsule_configured): infra_hosts = {'target_sat': target_sat, 'module_capsule_configured': module_capsule_configured} @@ -230,10 +210,9 @@ def test_positive_run_custom_job_template(self, rex_contenthost, module_org, tar @pytest.mark.tier3 @pytest.mark.upgrade - @pytest.mark.no_containers @pytest.mark.rhel_ver_list([8]) def test_positive_run_default_job_template_multiple_hosts( - self, registered_hosts, module_target_sat + self, rex_contenthosts, module_target_sat ): """Run default job template against multiple hosts @@ -243,7 +222,7 @@ def test_positive_run_default_job_template_multiple_hosts( :parametrized: yes """ - clients = registered_hosts + clients = rex_contenthosts invocation_command = module_target_sat.cli_factory.job_invocation( { 'job-template': 'Run Command - Script Default', @@ -622,392 +601,6 @@ def test_recurring_with_unreachable_host(self, module_target_sat, rhel_contentho assert cli.JobInvocation.info({'id': invocation.id})['failed'] != '0' -class TestAnsibleREX: - """Test class for remote execution via Ansible""" - - @pytest.mark.tier3 - @pytest.mark.upgrade - @pytest.mark.pit_client - @pytest.mark.pit_server - @pytest.mark.rhel_ver_list([7, 8, 9]) - def test_positive_run_effective_user_job(self, rex_contenthost, target_sat): - """Tests Ansible REX job having effective user runs successfully - - :id: a5fa20d8-c2bd-4bbf-a6dc-bf307b59dd8c - - :steps: - - 0. Create a VM and register to SAT and prepare for REX (ssh key) - - 1. Run Ansible Command job for the host to create a user - - 2. Run Ansible Command job using effective user - - 3. Check the job result at the host is done under that user - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :parametrized: yes - """ - client = rex_contenthost - # create a user on client via remote job - username = gen_string('alpha') - filename = gen_string('alpha') - make_user_job = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': f"command=useradd -m {username}", - 'search-query': f"name ~ {client.hostname}", - } - ) - assert_job_invocation_result(target_sat, make_user_job['id'], client.hostname) - # create a file as new user - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': f"command=touch /home/{username}/{filename}", - 'search-query': f"name ~ {client.hostname}", - 'effective-user': f'{username}', - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - # check the file owner - result = client.execute( - f'''stat -c '%U' /home/{username}/{filename}''', - ) - # assert the file is owned by the effective user - assert username == result.stdout.strip('\n'), "file ownership mismatch" - - @pytest.mark.tier3 - @pytest.mark.upgrade - @pytest.mark.rhel_ver_list([8]) - def test_positive_run_reccuring_job(self, rex_contenthost, target_sat): - """Tests Ansible REX reccuring job runs successfully multiple times - - :id: 49b0d31d-58f9-47f1-aa5d-561a1dcb0d66 - - :steps: - - 0. Create a VM and register to SAT and prepare for REX (ssh key) - - 1. Run recurring Ansible Command job for the host - - 2. Check the multiple job results at the host - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :customerscenario: true - - :bz: 2129432 - - :parametrized: yes - """ - client = rex_contenthost - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': 'command=ls', - 'search-query': f"name ~ {client.hostname}", - 'cron-line': '* * * * *', # every minute - 'max-iteration': 2, # just two runs - } - ) - result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) - sleep(150) - rec_logic = target_sat.cli.RecurringLogic.info({'id': result['recurring-logic-id']}) - assert rec_logic['state'] == 'finished' - assert rec_logic['iteration'] == '2' - # 2129432 - rec_logic_keys = rec_logic.keys() - assert 'action' in rec_logic_keys - assert 'last-occurrence' in rec_logic_keys - assert 'next-occurrence' in rec_logic_keys - assert 'state' in rec_logic_keys - assert 'purpose' in rec_logic_keys - assert 'iteration' in rec_logic_keys - assert 'iteration-limit' in rec_logic_keys - - @pytest.mark.tier3 - @pytest.mark.no_containers - def test_positive_run_concurrent_jobs(self, registered_hosts, target_sat): - """Tests Ansible REX concurent jobs without batch trigger - - :id: ad0f108c-03f2-49c7-8732-b1056570567b - - :steps: - - 0. Create 2 hosts, disable foreman_tasks_proxy_batch_trigger - - 1. Run Ansible Command job with concurrency-setting - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :customerscenario: true - - :BZ: 1817320 - - :parametrized: yes - """ - param_name = 'foreman_tasks_proxy_batch_trigger' - target_sat.cli.GlobalParameter().set({'name': param_name, 'value': 'false'}) - clients = registered_hosts - output_msgs = [] - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Run Command - Ansible Default', - 'inputs': 'command=ls', - 'search-query': f'name ~ {clients[0].hostname} or name ~ {clients[1].hostname}', - 'concurrency-level': 2, - } - ) - for vm in clients: - output_msgs.append( - 'host output from {}: {}'.format( - vm.hostname, - ' '.join( - target_sat.cli.JobInvocation.get_output( - {'id': invocation_command['id'], 'host': vm.hostname} - ) - ), - ) - ) - result = target_sat.cli.JobInvocation.info({'id': invocation_command['id']}) - assert result['success'] == '2', output_msgs - target_sat.cli.GlobalParameter().delete({'name': param_name}) - assert len(target_sat.cli.GlobalParameter().list({'search': param_name})) == 0 - - @pytest.mark.tier3 - @pytest.mark.no_containers - def test_positive_run_serial(self, registered_hosts, target_sat): - """Tests subtasks in a job run one by one when concurrency level set to 1 - - :id: 5ce39447-82d0-42df-81be-16ed3d67a2a4 - - :Setup: - 0. Create 2 hosts - - :steps: - - 0. Run a bash command job with concurrency level 1 - - :expectedresults: First subtask should run immediately, second one after the first one finishes - - :CaseAutomation: Automated - - :parametrized: yes - """ - hosts = registered_hosts - output_msgs = [] - template_file = f"/root/{gen_string('alpha')}.template" - target_sat.execute( - f"echo 'rm /root/test-<%= @host %>; echo $(date +%s) >> /root/test-<%= @host %>; sleep 120; echo $(date +%s) >> /root/test-<%= @host %>' > {template_file}" - ) - template = target_sat.cli.JobTemplate.create( - { - 'name': gen_string('alpha'), - 'file': template_file, - 'job-category': 'Commands', - 'provider-type': 'script', - } - ) - invocation = target_sat.cli_factory.job_invocation( - { - 'job-template': template['name'], - 'search-query': f'name ~ {hosts[0].hostname} or name ~ {hosts[1].hostname}', - 'concurrency-level': 1, - } - ) - for vm in hosts: - output_msgs.append( - 'host output from {}: {}'.format( - vm.hostname, - ' '.join( - target_sat.cli.JobInvocation.get_output( - {'id': invocation['id'], 'host': vm.hostname} - ) - ), - ) - ) - result = target_sat.cli.JobInvocation.info({'id': invocation['id']}) - assert result['success'] == '2', output_msgs - # assert for time diffs - file1 = hosts[0].execute('cat /root/test-$(hostname)').stdout - file2 = hosts[1].execute('cat /root/test-$(hostname)').stdout - file1_start, file1_end = map(int, file1.rstrip().split('\n')) - file2_start, file2_end = map(int, file2.rstrip().split('\n')) - if file1_start > file2_start: - file1_start, file1_end, file2_start, file2_end = ( - file2_start, - file2_end, - file1_start, - file1_end, - ) - assert file1_end - file1_start >= 120 - assert file2_end - file2_start >= 120 - assert file2_start >= file1_end # the jobs did NOT run concurrently - - @pytest.mark.tier3 - @pytest.mark.upgrade - @pytest.mark.e2e - @pytest.mark.no_containers - @pytest.mark.pit_server - @pytest.mark.rhel_ver_match('[^6].*') - @pytest.mark.skipif( - (not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url' - ) - def test_positive_run_packages_and_services_job( - self, rhel_contenthost, module_org, module_ak_with_cv, target_sat - ): - """Tests Ansible REX job can install packages and start services - - :id: 47ed82fb-77ca-43d6-a52e-f62bae5d3a42 - - :steps: - - 0. Create a VM and register to SAT and prepare for REX (ssh key) - - 1. Run Ansible Package job for the host to install a package - - 2. Check the package is present at the host - - 3. Run Ansible Service job for the host to start a service - - 4. Check the service is started on the host - - :expectedresults: multiple asserts along the code - - :CaseAutomation: Automated - - :bz: 1872688, 1811166 - - :CaseImportance: Critical - - :customerscenario: true - - :parametrized: yes - """ - client = rhel_contenthost - packages = ['tapir'] - client.register( - module_org, - None, - module_ak_with_cv.name, - target_sat, - repo=settings.repos.yum_3.url, - ) - # install package - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Package Action - Ansible Default', - 'inputs': 'state=latest, name={}'.format(*packages), - 'search-query': f'name ~ {client.hostname}', - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - result = client.run(f'rpm -q {" ".join(packages)}') - assert result.status == 0 - - # stop a service - service = "rsyslog" - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Service Action - Ansible Default', - 'inputs': f'state=stopped, name={service}', - 'search-query': f"name ~ {client.hostname}", - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - result = client.execute(f'systemctl status {service}') - assert result.status == 3 - - # start it again - invocation_command = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Service Action - Ansible Default', - 'inputs': f'state=started, name={service}', - 'search-query': f'name ~ {client.hostname}', - } - ) - assert_job_invocation_result(target_sat, invocation_command['id'], client.hostname) - result = client.execute(f'systemctl status {service}') - assert result.status == 0 - - @pytest.mark.tier3 - @pytest.mark.parametrize( - 'fixture_sca_vmsetup', [{'nick': 'rhel8'}], ids=['rhel8'], indirect=True - ) - def test_positive_install_ansible_collection( - self, fixture_sca_vmsetup, module_sca_manifest_org, target_sat - ): - """Test whether Ansible collection can be installed via REX - - :steps: - 1. Upload a manifest. - 2. Enable and sync Ansible repository. - 3. Register content host to Satellite. - 4. Enable Ansible repo on content host. - 5. Install ansible package. - 6. Run REX job to install Ansible collection on content host. - - :id: ad25aee5-4ea3-4743-a301-1c6271856f79 - - :CaseComponent: Ansible-RemoteExecution - - :Team: Rocket - """ - # Configure repository to prepare for installing ansible on host - target_sat.cli.RepositorySet.enable( - { - 'basearch': 'x86_64', - 'name': REPOSET['rhae2.9_el8'], - 'organization-id': module_sca_manifest_org.id, - 'product': PRDS['rhae'], - 'releasever': '8', - } - ) - target_sat.cli.Repository.synchronize( - { - 'name': REPOS['rhae2.9_el8']['name'], - 'organization-id': module_sca_manifest_org.id, - 'product': PRDS['rhae'], - } - ) - client = fixture_sca_vmsetup - client.execute('subscription-manager refresh') - client.execute(f'subscription-manager repos --enable {REPOS["rhae2.9_el8"]["id"]}') - client.execute('dnf -y install ansible') - collection_job = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Ansible Collection - Install from Galaxy', - 'inputs': 'ansible_collections_list="oasis_roles.system"', - 'search-query': f'name ~ {client.hostname}', - } - ) - result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) - assert result['success'] == '1' - collection_path = client.execute('ls /etc/ansible/collections/ansible_collections').stdout - assert 'oasis_roles' in collection_path - - # Extend test with custom collections_path advanced input field - collection_job = target_sat.cli_factory.job_invocation( - { - 'job-template': 'Ansible Collection - Install from Galaxy', - 'inputs': 'ansible_collections_list="oasis_roles.system", collections_path="~/"', - 'search-query': f'name ~ {client.hostname}', - } - ) - result = target_sat.cli.JobInvocation.info({'id': collection_job['id']}) - assert result['success'] == '1' - collection_path = client.execute('ls ~/ansible_collections').stdout - assert 'oasis_roles' in collection_path - - class TestRexUsers: """Tests related to remote execution users""" diff --git a/tests/foreman/cli/test_rhcloud_inventory.py b/tests/foreman/cli/test_rhcloud_inventory.py index 31847984e03..ed84fd465d7 100644 --- a/tests/foreman/cli/test_rhcloud_inventory.py +++ b/tests/foreman/cli/test_rhcloud_inventory.py @@ -6,7 +6,7 @@ :CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/cli/test_subscription.py b/tests/foreman/cli/test_subscription.py index a7a74f4360c..c28ba08e61a 100644 --- a/tests/foreman/cli/test_subscription.py +++ b/tests/foreman/cli/test_subscription.py @@ -12,10 +12,12 @@ """ from fauxfactory import gen_string +from manifester import Manifester from nailgun import entities import pytest -from robottelo.constants import PRDS, REPOS, REPOSET +from robottelo.config import settings +from robottelo.constants import EXPIRED_MANIFEST, PRDS, REPOS, REPOSET, DataFile from robottelo.exceptions import CLIReturnCodeError pytestmark = [pytest.mark.run_in_one_thread] @@ -276,3 +278,44 @@ def test_positive_auto_attach_disabled_golden_ticket( with pytest.raises(CLIReturnCodeError) as context: target_sat.cli.Host.subscription_auto_attach({'host-id': host_id}) assert "This host's organization is in Simple Content Access mode" in str(context.value) + + +def test_negative_check_katello_reimport(target_sat, function_org): + """Verify katello:reimport trace should not fail with an TypeError + + :id: b7508a1c-7798-4649-83a3-cf94c7409c96 + + :steps: + 1. Import expired manifest & refresh + 2. Delete expired manifest + 3. Re-import new valid manifest & refresh + + :expectedresults: There should not be an error after reimport manifest + + :customerscenario: true + + :BZ: 2225534, 2253621 + """ + remote_path = f'/tmp/{EXPIRED_MANIFEST}' + target_sat.put(DataFile.EXPIRED_MANIFEST_FILE, remote_path) + # Import expired manifest & refresh + target_sat.cli.Subscription.upload({'organization-id': function_org.id, 'file': remote_path}) + with pytest.raises(CLIReturnCodeError): + target_sat.cli.Subscription.refresh_manifest({'organization-id': function_org.id}) + exec_val = target_sat.execute( + 'grep -i "Katello::HttpErrors::BadRequest: This Organization\'s subscription ' + 'manifest has expired. Please import a new manifest" /var/log/foreman/production.log' + ) + assert exec_val.status + # Delete expired manifest + target_sat.cli.Subscription.delete_manifest({'organization-id': function_org.id}) + # Re-import new manifest & refresh + manifester = Manifester(manifest_category=settings.manifest.golden_ticket) + manifest = manifester.get_manifest() + target_sat.upload_manifest(function_org.id, manifest.content) + ret_val = target_sat.cli.Subscription.refresh_manifest({'organization-id': function_org.id}) + assert 'Candlepin job status: SUCCESS' in ret_val + # Additional check, katello:reimport trace should not fail with TypeError + trace_output = target_sat.execute("foreman-rake katello:reimport --trace") + assert 'TypeError: no implicit conversion of String into Integer' not in trace_output.stdout + assert trace_output.status == 0 diff --git a/tests/foreman/data/expired-manifest.zip b/tests/foreman/data/expired-manifest.zip new file mode 100644 index 00000000000..8737ba42db2 Binary files /dev/null and b/tests/foreman/data/expired-manifest.zip differ diff --git a/tests/foreman/destructive/test_ansible.py b/tests/foreman/destructive/test_ansible.py index 01779d6a14a..b57ba89c58f 100644 --- a/tests/foreman/destructive/test_ansible.py +++ b/tests/foreman/destructive/test_ansible.py @@ -13,7 +13,7 @@ """ import pytest -pytestmark = pytest.mark.destructive +pytestmark = [pytest.mark.destructive, pytest.mark.upgrade] def test_positive_persistent_ansible_cfg_change(target_sat): diff --git a/tests/foreman/destructive/test_capsule_loadbalancer.py b/tests/foreman/destructive/test_capsule_loadbalancer.py index 3949d2bf71a..cf36aac8a9f 100644 --- a/tests/foreman/destructive/test_capsule_loadbalancer.py +++ b/tests/foreman/destructive/test_capsule_loadbalancer.py @@ -14,6 +14,7 @@ import pytest from wrapanapi import VmState +from robottelo import constants from robottelo.config import settings from robottelo.constants import CLIENT_PORT, DataFile from robottelo.utils.installer import InstallerCommand @@ -22,34 +23,50 @@ @pytest.fixture(scope='module') -def content_for_client(module_target_sat, module_org, module_lce, module_cv, module_ak): +def content_for_client(module_target_sat, module_sca_manifest_org, module_lce, module_cv): """Setup content to be used by haproxy and client :return: Activation key, client lifecycle environment(used by setup_capsules()) """ - module_target_sat.cli_factory.setup_org_for_a_custom_repo( - { - 'url': settings.repos.RHEL7_OS, - 'organization-id': module_org.id, - 'content-view-id': module_cv.id, - 'lifecycle-environment-id': module_lce.id, - 'activationkey-id': module_ak.id, - } - ) - return {'client_ak': module_ak, 'client_lce': module_lce} + rhel_ver = settings.content_host.default_rhel_version + baseos = f'rhel{rhel_ver}_bos' + appstream = f'rhel{rhel_ver}_aps' + + rh_repos = [] + for repo in [baseos, appstream]: + synced_repo_id = module_target_sat.api_factory.enable_sync_redhat_repo( + constants.REPOS[repo], module_sca_manifest_org.id + ) + repo = module_target_sat.api.Repository(id=synced_repo_id).read() + rh_repos.append(repo) + + module_cv.repository = rh_repos + module_cv.update(['repository']) + module_cv.publish() + module_cv = module_cv.read() + cvv = module_cv.version[0] + cvv.promote(data={'environment_ids': module_lce.id}) + module_cv = module_cv.read() + ak = module_target_sat.api.ActivationKey( + content_view=module_cv, + environment=module_lce, + organization=module_sca_manifest_org, + ).create() + + return {'client_ak': ak, 'client_lce': module_lce} @pytest.fixture(scope='module') def setup_capsules( module_org, - rhel7_contenthost_module, + module_rhel_contenthost, module_lb_capsule, module_target_sat, content_for_client, ): """Install capsules with loadbalancer options""" - extra_cert_var = {'foreman-proxy-cname': rhel7_contenthost_module.hostname} - extra_installer_var = {'certs-cname': rhel7_contenthost_module.hostname} + extra_cert_var = {'foreman-proxy-cname': module_rhel_contenthost.hostname} + extra_installer_var = {'certs-cname': module_rhel_contenthost.hostname} for capsule in module_lb_capsule: capsule.register_to_cdn() @@ -92,20 +109,20 @@ def setup_capsules( @pytest.fixture(scope='module') def setup_haproxy( module_org, - rhel7_contenthost_module, + module_rhel_contenthost, content_for_client, module_target_sat, setup_capsules, ): """Install and configure haproxy and setup logging""" - haproxy = rhel7_contenthost_module + haproxy = module_rhel_contenthost # Using same AK for haproxy just for packages haproxy_ak = content_for_client['client_ak'] haproxy.execute('firewall-cmd --add-service RH-Satellite-6-capsule') haproxy.execute('firewall-cmd --runtime-to-permanent') haproxy.install_katello_ca(module_target_sat) haproxy.register_contenthost(module_org.label, haproxy_ak.name) - result = haproxy.execute('yum install haproxy policycoreutils-python -y') + result = haproxy.execute('yum install haproxy policycoreutils-python-utils -y') assert result.status == 0 haproxy.execute('rm -f /etc/haproxy/haproxy.cfg') haproxy.session.sftp_write( @@ -171,8 +188,9 @@ def loadbalancer_setup( @pytest.mark.e2e @pytest.mark.tier1 +@pytest.mark.rhel_ver_list([settings.content_host.default_rhel_version]) def test_loadbalancer_install_package( - loadbalancer_setup, setup_capsules, rhel7_contenthost, module_org, module_location, request + loadbalancer_setup, setup_capsules, rhel_contenthost, module_org, module_location, request ): r"""Install packages on a content host regardless of the registered capsule being available @@ -193,7 +211,7 @@ def test_loadbalancer_install_package( """ # Register content host - result = rhel7_contenthost.register( + result = rhel_contenthost.register( org=module_org, loc=module_location, activation_keys=loadbalancer_setup['content_for_client']['client_ak'].name, @@ -203,15 +221,15 @@ def test_loadbalancer_install_package( assert result.status == 0, f'Failed to register host: {result.stderr}' # Try package installation - result = rhel7_contenthost.execute('yum install -y tree') + result = rhel_contenthost.execute('yum install -y tree') assert result.status == 0 hosts = loadbalancer_setup['module_target_sat'].cli.Host.list( {'organization-id': loadbalancer_setup['module_org'].id} ) - assert rhel7_contenthost.hostname in [host['name'] for host in hosts] + assert rhel_contenthost.hostname in [host['name'] for host in hosts] - result = rhel7_contenthost.execute('rpm -qa | grep katello-ca-consumer') + result = rhel_contenthost.execute('rpm -qa | grep katello-ca-consumer') # Find which capsule the host is registered to since it's RoundRobin # The following also asserts the above result @@ -225,14 +243,14 @@ def test_loadbalancer_install_package( ) # Remove the packages from the client - result = rhel7_contenthost.execute('yum remove -y tree') + result = rhel_contenthost.execute('yum remove -y tree') assert result.status == 0 # Power off the capsule that the client is registered to registered_to_capsule.power_control(state=VmState.STOPPED, ensure=True) # Try package installation again - result = rhel7_contenthost.execute('yum install -y tree') + result = rhel_contenthost.execute('yum install -y tree') assert result.status == 0 diff --git a/tests/foreman/endtoend/test_cli_endtoend.py b/tests/foreman/endtoend/test_cli_endtoend.py index 30de18bd960..dc44d6691f7 100644 --- a/tests/foreman/endtoend/test_cli_endtoend.py +++ b/tests/foreman/endtoend/test_cli_endtoend.py @@ -70,7 +70,7 @@ def test_positive_cli_find_admin_user(module_target_sat): @pytest.mark.e2e @pytest.mark.upgrade @pytest.mark.skipif((not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url') -def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel_contenthost): +def test_positive_cli_end_to_end(function_sca_manifest, target_sat, rhel_contenthost): """Perform end to end smoke tests using RH and custom repos. 1. Create a new user with admin permissions @@ -110,12 +110,11 @@ def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel # step 2.1: Create a new organization org = _create(user, target_sat.cli.Org, {'name': gen_alphanumeric()}) - target_sat.cli.SimpleContentAccess.disable({'organization-id': org['id']}) # step 2.2: Clone and upload manifest - target_sat.put(f'{function_entitlement_manifest.path}', f'{function_entitlement_manifest.name}') + target_sat.put(f'{function_sca_manifest.path}', f'{function_sca_manifest.name}') target_sat.cli.Subscription.upload( - {'file': f'{function_entitlement_manifest.name}', 'organization-id': org['id']} + {'file': f'{function_sca_manifest.name}', 'organization-id': org['id']} ) # step 2.3: Create a new lifecycle environment @@ -226,20 +225,9 @@ def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel ) # step 2.13: Add the products to the activation key - subscription_list = target_sat.cli.Subscription.with_user(user['login'], user['password']).list( + target_sat.cli.Subscription.with_user(user['login'], user['password']).list( {'organization-id': org['id']}, per_page=False ) - for subscription in subscription_list: - if subscription['name'] == constants.DEFAULT_SUBSCRIPTION_NAME: - target_sat.cli.ActivationKey.with_user( - user['login'], user['password'] - ).add_subscription( - { - 'id': activation_key['id'], - 'quantity': 1, - 'subscription-id': subscription['id'], - } - ) # step 2.13.1: Enable product content target_sat.cli.ActivationKey.with_user(user['login'], user['password']).content_override( @@ -266,14 +254,22 @@ def test_positive_cli_end_to_end(function_entitlement_manifest, target_sat, rhel ) content_host = target_sat.cli.Host.with_user(user['login'], user['password']).info( - {'id': content_host['id']} + {'id': content_host['id']}, output_format='json' ) + # check that content view matches what we passed - assert content_host['content-information']['content-view']['name'] == content_view['name'] + assert ( + content_host['content-information']['content-view-environments']['1']['content-view'][ + 'name' + ] + == content_view['name'] + ) # check that lifecycle environment matches assert ( - content_host['content-information']['lifecycle-environment']['name'] + content_host['content-information']['content-view-environments']['1'][ + 'lifecycle-environment' + ]['name'] == lifecycle_environment['name'] ) diff --git a/tests/foreman/maintain/test_service.py b/tests/foreman/maintain/test_service.py index 7f8c0efad8c..3ff33790055 100644 --- a/tests/foreman/maintain/test_service.py +++ b/tests/foreman/maintain/test_service.py @@ -225,7 +225,9 @@ def test_positive_foreman_service(sat_maintain): assert 'foreman' in result.stdout result = sat_maintain.cli.Service.status(options={'only': 'httpd'}) assert result.status == 0 - result = sat_maintain.cli.Health.check(options={'assumeyes': True}) + result = sat_maintain.cli.Health.check( + options={'assumeyes': True, 'whitelist': 'check-tftp-storage'} + ) assert result.status == 0 assert 'foreman' in result.stdout assert sat_maintain.cli.Service.start(options={'only': 'foreman'}).status == 0 diff --git a/tests/foreman/maintain/test_upgrade.py b/tests/foreman/maintain/test_upgrade.py index 0f08f23c6b3..a79094f7a6b 100644 --- a/tests/foreman/maintain/test_upgrade.py +++ b/tests/foreman/maintain/test_upgrade.py @@ -98,8 +98,14 @@ def test_positive_repositories_validate(sat_maintain): @pytest.mark.parametrize( 'custom_host', [ - {'deploy_rhel_version': '8', 'deploy_flavor': 'satqe-ssd.disk.xxxl'}, - {'deploy_rhel_version': '8', 'deploy_flavor': 'satqe-ssd.standard.std'}, + { + 'deploy_rhel_version': settings.server.version.rhel_version, + 'deploy_flavor': 'satqe-ssd.disk.xxxl', + }, + { + 'deploy_rhel_version': settings.server.version.rhel_version, + 'deploy_flavor': 'satqe-ssd.standard.std', + }, ], ids=['default', 'medium'], indirect=True, @@ -118,15 +124,18 @@ def test_negative_pre_upgrade_tuning_profile_check(request, custom_host): :expectedresults: Pre-upgrade check fails. """ profile = request.node.callspec.id + rhel_major = custom_host.os_version.major sat_version = ".".join(settings.server.version.release.split('.')[0:2]) - # Register to CDN for RHEL8 repos, download and enable last y stream's ohsnap repos, + # Register to CDN for RHEL repos, download and enable last y stream's ohsnap repos, # and enable the satellite module and install it on the host custom_host.register_to_cdn() last_y_stream = last_y_stream_version( SATELLITE_VERSION if sat_version == 'stream' else sat_version ) custom_host.download_repofile(product='satellite', release=last_y_stream) - custom_host.execute('dnf -y module enable satellite:el8 && dnf -y install satellite') + custom_host.execute( + f'dnf -y module enable satellite:el{rhel_major} && dnf -y install satellite' + ) # Install with development tuning profile to get around installer checks custom_host.execute( 'satellite-installer --scenario satellite --tuning development', diff --git a/tests/foreman/ui/test_ansible.py b/tests/foreman/ui/test_ansible.py index 89eba62ff56..57fae0a8731 100644 --- a/tests/foreman/ui/test_ansible.py +++ b/tests/foreman/ui/test_ansible.py @@ -275,6 +275,81 @@ def test_positive_host_role_information(target_sat, function_host): assert all_assigned_roles_table[0]["Name"] == SELECTED_ROLE +@pytest.mark.rhel_ver_match('8') +def test_positive_assign_ansible_role_variable_on_host( + target_sat, rhel_contenthost, module_activation_key, module_org, module_location, request +): + """Verify ansible variable is added to the role and attached to the host. + + :id: 7ec4fe19-5a08-4b10-bb4e-7327dd68699a + + :BZ: 2170727 + + :customerscenario: true + + :steps: + + 1. Create an Ansible variable with array type and set the default value. + 2. Enable both 'Merge Overrides' and 'Merge Default'. + 3. Add the variable to a role and attach the role to the host. + 4. Verify that ansible role and variable is added to the host. + + :expectedresults: The role and variable is successfully added to the host. + """ + + @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'] + + key = gen_string('alpha') + SELECTED_ROLE = 'redhat.satellite.activation_keys' + proxy_id = target_sat.nailgun_smart_proxy.id + target_sat.api.AnsibleRoles().sync(data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]}) + command = target_sat.api.RegistrationCommand( + organization=module_org, + location=module_location, + activation_keys=[module_activation_key.name], + ).create() + result = rhel_contenthost.execute(command) + assert result.status == 0, f'Failed to register host: {result.stderr}' + target_host = rhel_contenthost.nailgun_host + default_value = '[\"test\"]' + parameter_type = 'array' + with target_sat.ui_session() as session: + session.organization.select(org_name=module_org.name) + session.location.select(loc_name=module_location.name) + session.ansiblevariables.create_with_overrides( + { + 'key': key, + 'ansible_role': SELECTED_ROLE, + 'override': 'true', + 'parameter_type': parameter_type, + 'default_value': default_value, + 'validator_type': None, + 'attribute_order': 'domain \n fqdn \n hostgroup \n os', + 'merge_default': 'true', + 'merge_overrides': 'true', + 'matcher_section.params': [ + { + 'attribute_type': {'matcher_key': 'os', 'matcher_value': 'fedora'}, + 'value': '[\'13\']', + } + ], + } + ) + result = target_sat.cli.Host.ansible_roles_assign( + {'id': target_host.id, 'ansible-roles': SELECTED_ROLE} + ) + assert 'Ansible roles were assigned' in result[0]['message'] + values = session.host_new.get_details(rhel_contenthost.hostname, 'ansible')['ansible'][ + 'variables' + ]['table'] + assert (key, SELECTED_ROLE, default_value, parameter_type) in [ + (var['Name'], var['Ansible role'], var['Value'], var['Type']) for var in values + ] + + @pytest.mark.stubbed @pytest.mark.tier2 def test_positive_role_variable_information(self): diff --git a/tests/foreman/ui/test_bookmarks.py b/tests/foreman/ui/test_bookmarks.py index 563a19102a7..096f8fdf7cd 100644 --- a/tests/foreman/ui/test_bookmarks.py +++ b/tests/foreman/ui/test_bookmarks.py @@ -11,16 +11,18 @@ :CaseImportance: High """ -from airgun.exceptions import NoSuchElementException +from airgun.exceptions import DisabledWidgetError, NoSuchElementException from fauxfactory import gen_string import pytest from robottelo.config import user_nailgun_config -from robottelo.constants import BOOKMARK_ENTITIES +from robottelo.constants import BOOKMARK_ENTITIES_SELECTION @pytest.fixture( - scope='module', params=BOOKMARK_ENTITIES, ids=(i['name'] for i in BOOKMARK_ENTITIES) + scope='module', + params=BOOKMARK_ENTITIES_SELECTION, + ids=(i['name'] for i in BOOKMARK_ENTITIES_SELECTION), ) def ui_entity(module_org, module_location, request): """Collects the list of all applicable UI entities for testing and does all @@ -256,8 +258,14 @@ def test_negative_create_with_duplicate_name(session, ui_entity, module_target_s existing_bookmark = session.bookmark.search(bookmark.name)[0] assert existing_bookmark['Name'] == bookmark.name ui_lib = getattr(session, ui_entity['name'].lower()) - # this fails but does not raise UI error, BZ#1992652 closed wontfix - ui_lib.create_bookmark({'name': bookmark.name, 'query': query, 'public': True}) + # this fails but does not raise UI error in old style dialog, BZ#1992652 closed + # wontfix, but new style dialog raises error, both situations occur + old_ui = ui_entity.get('old_ui') + if old_ui: + ui_lib.create_bookmark({'name': bookmark.name, 'query': query, 'public': True}) + else: + with pytest.raises((DisabledWidgetError, NoSuchElementException)): + ui_lib.create_bookmark({'name': bookmark.name, 'query': query, 'public': True}) # assert there are no duplicate bookmarks new_search = session.bookmark.search(bookmark.name) assert len(new_search) == 1 diff --git a/tests/foreman/ui/test_computeresource_vmware.py b/tests/foreman/ui/test_computeresource_vmware.py index ddf7ca5f33e..82e9d0abaa6 100644 --- a/tests/foreman/ui/test_computeresource_vmware.py +++ b/tests/foreman/ui/test_computeresource_vmware.py @@ -26,6 +26,7 @@ VMWARE_CONSTANTS, ) from robottelo.utils.datafactory import gen_string +from robottelo.utils.issue_handlers import is_open pytestmark = [pytest.mark.skip_if_not_set('vmware')] @@ -79,9 +80,10 @@ def _get_vmware_datastore_summary_string(data_store_name=settings.vmware.datasto return f'{data_store_name} (free: {free_space}, prov: {prov}, total: {capacity})' +@pytest.mark.e2e @pytest.mark.tier1 @pytest.mark.parametrize('vmware', ['vmware7', 'vmware8'], indirect=True) -def test_positive_end_to_end(session, module_org, module_location, vmware, module_target_sat): +def test_positive_cr_end_to_end(session, module_org, module_location, vmware, module_target_sat): """Perform end-to-end testing for compute resource VMware component. :id: 47fc9e77-5b22-46b4-a76c-3217434fde2f @@ -289,32 +291,64 @@ def test_positive_resource_vm_power_management(session, vmware): raise AssertionError('Timed out waiting for VM to toggle power state') from err +@pytest.mark.e2e +@pytest.mark.upgrade @pytest.mark.tier2 @pytest.mark.parametrize('vmware', ['vmware7', 'vmware8'], indirect=True) -def test_positive_select_vmware_custom_profile_guest_os_rhel7(session, vmware): - """Select custom default (3-Large) compute profile guest OS RHEL7. +def test_positive_vmware_custom_profile_end_to_end(session, vmware, request, target_sat): + """Perform end to end testing for VMware compute profile. :id: 24f7bb5f-2aaf-48cb-9a56-d2d0713dfe3d :customerscenario: true - :setup: vmware hostname and credentials. - :steps: 1. Create a compute resource of type vmware. - 2. Provide valid hostname, username and password. - 3. Select the created vmware CR. - 4. Click Compute Profile tab. - 5. Select 3-Large profile - 6. Set Guest OS field to RHEL7 OS. + 2. Update a compute profile with all values - :expectedresults: Guest OS RHEL7 is selected successfully. + :expectedresults: Compute profiles are updated successfully with all the values. - :BZ: 1315277 + :BZ: 1315277, 2266672 """ cr_name = gen_string('alpha') - guest_os_name = 'Red Hat Enterprise Linux 7 (64-bit)' + guest_os_names = [ + 'Red Hat Enterprise Linux 7 (64-bit)', + 'Red Hat Enterprise Linux 8 (64 bit)', + 'Red Hat Enterprise Linux 9 (64 bit)', + ] + compute_profile = ['1-Small', '2-Medium', '3-Large'] + cpus = ['2', '4', '6'] + vm_memory = ['4000', '6000', '8000'] + annotation_notes = gen_string('alpha') + firmware_type = ['Automatic', 'BIOS', 'EFI'] + resource_pool = VMWARE_CONSTANTS['pool'] + folder = VMWARE_CONSTANTS['folder'] + virtual_hw_version = VMWARE_CONSTANTS['virtualhw_version'] + memory_hot_add = True + cpu_hot_add = True + cdrom_drive = True + disk_size = '10 GB' + network = 'VLAN 1001' # hardcoding network here as this test won't be doing actual provisioning + data_store_summary_string = _get_vmware_datastore_summary_string(vmware=vmware) + storage_data = { + 'storage': { + 'controller': VMWARE_CONSTANTS['scsicontroller'], + 'disks': [ + { + 'data_store': data_store_summary_string, + 'size': disk_size, + 'thin_provision': True, + } + ], + } + } + network_data = { + 'network_interfaces': { + 'nic_type': VMWARE_CONSTANTS['network_interface_name'], + 'network': network, + } + } with session: session.computeresource.create( { @@ -326,143 +360,65 @@ def test_positive_select_vmware_custom_profile_guest_os_rhel7(session, vmware): 'provider_content.datacenter.value': settings.vmware.datacenter, } ) - assert session.computeresource.search(cr_name)[0]['Name'] == cr_name - session.computeresource.update_computeprofile( - cr_name, COMPUTE_PROFILE_LARGE, {'provider_content.guest_os': guest_os_name} - ) - values = session.computeresource.read_computeprofile(cr_name, COMPUTE_PROFILE_LARGE) - assert values['provider_content']['guest_os'] == guest_os_name - - -@pytest.mark.tier2 -@pytest.mark.parametrize('vmware', ['vmware7', 'vmware8'], indirect=True) -def test_positive_access_vmware_with_custom_profile(session, vmware): - """Associate custom default (3-Large) compute profile - :id: 751ef765-5091-4322-a0d9-0c9c73009cc4 + @request.addfinalizer + def _finalize(): + cr = target_sat.api.VMWareComputeResource().search(query={'search': f'name={cr_name}'}) + if cr: + target_sat.api.VMWareComputeResource(id=cr[0].id).delete() - :setup: vmware hostname and credentials. - - :steps: - - 1. Create a compute resource of type vmware. - 2. Provide valid hostname, username and password. - 3. Select the created vmware CR. - 4. Click Compute Profile tab. - 5. Edit (3-Large) with valid configurations and submit. - - :expectedresults: The Compute Resource created and associated to compute profile (3-Large) - with provided values. - """ - cr_name = gen_string('alpha') - data_store_summary_string = _get_vmware_datastore_summary_string(vmware=vmware) - cr_profile_data = dict( - cpus='2', - cores_per_socket='2', - memory='1024', - firmware='EFI', - cluster=settings.vmware.cluster, - resource_pool=VMWARE_CONSTANTS.get('pool'), - folder=VMWARE_CONSTANTS.get('folder'), - guest_os=VMWARE_CONSTANTS.get('guest_os'), - virtual_hw_version=VMWARE_CONSTANTS.get('virtualhw_version'), - memory_hot_add=True, - cpu_hot_add=True, - cdrom_drive=True, - annotation_notes=gen_string('alpha'), - network_interfaces=[] - if not settings.provisioning.vlan_id - else [ - dict( - nic_type=VMWARE_CONSTANTS.get('network_interface_name'), - network='VLAN 1001', # hardcoding network here as these test won't be doing actual provisioning - ), - dict( - nic_type=VMWARE_CONSTANTS.get('network_interface_name'), - network='VLAN 1001', - ), - ], - storage=[ - dict( - controller=VMWARE_CONSTANTS.get('scsicontroller'), - disks=[ - dict( - data_store=data_store_summary_string, - size='10 GB', - thin_provision=True, - ), - dict( - data_store=data_store_summary_string, - size='20 GB', - thin_provision=False, - eager_zero=False, - ), - ], - ), - dict( - controller=VMWARE_CONSTANTS.get('scsicontroller'), - disks=[ - dict( - data_store=data_store_summary_string, - size='30 GB', - thin_provision=False, - eager_zero=True, - ) - ], - ), - ], - ) - with session: - session.computeresource.create( - { - 'name': cr_name, - 'provider': FOREMAN_PROVIDERS['vmware'], - 'provider_content.vcenter': vmware.hostname, - 'provider_content.user': settings.vmware.username, - 'provider_content.password': settings.vmware.password, - 'provider_content.datacenter.value': settings.vmware.datacenter, - } - ) assert session.computeresource.search(cr_name)[0]['Name'] == cr_name - session.computeresource.update_computeprofile( - cr_name, - COMPUTE_PROFILE_LARGE, - {f'provider_content.{key}': value for key, value in cr_profile_data.items()}, - ) - values = session.computeresource.read_computeprofile(cr_name, COMPUTE_PROFILE_LARGE) - provider_content = values['provider_content'] - # assert main compute resource profile data updated successfully. - excluded_keys = ['network_interfaces', 'storage'] - expected_value = { - key: value for key, value in cr_profile_data.items() if key not in excluded_keys - } - provided_value = { - key: value for key, value in provider_content.items() if key in expected_value - } - assert provided_value == expected_value - # assert compute resource profile network data updated successfully. - for network_index, expected_network_value in enumerate( - cr_profile_data['network_interfaces'] + for guest_os_name, cprofile, cpu, memory, firmware in zip( + guest_os_names, compute_profile, cpus, vm_memory, firmware_type, strict=True ): - provided_network_value = { - key: value - for key, value in provider_content['network_interfaces'][network_index].items() - if key in expected_network_value - } - assert provided_network_value == expected_network_value - # assert compute resource profile storage data updated successfully. - for controller_index, expected_controller_value in enumerate(cr_profile_data['storage']): - provided_controller_value = provider_content['storage'][controller_index] + session.computeresource.update_computeprofile( + cr_name, + cprofile, + { + 'provider_content.guest_os': guest_os_name, + 'provider_content.cpus': cpu, + 'provider_content.memory': memory, + 'provider_content.cluster': settings.vmware.cluster, + 'provider_content.annotation_notes': annotation_notes, + 'provider_content.virtual_hw_version': virtual_hw_version, + 'provider_content.firmware': firmware, + 'provider_content.resource_pool': resource_pool, + 'provider_content.folder': folder, + 'provider_content.memory_hot_add': memory_hot_add, + 'provider_content.cpu_hot_add': cpu_hot_add, + 'provider_content.cdrom_drive': cdrom_drive, + 'provider_content.storage': [value for value in storage_data.values()], + 'provider_content.network_interfaces': [ + value for value in network_data.values() + ], + }, + ) + values = session.computeresource.read_computeprofile(cr_name, cprofile) + provider_content = values['provider_content'] + assert provider_content['guest_os'] == guest_os_name + assert provider_content['cpus'] == cpu + assert provider_content['memory'] == memory + assert provider_content['cluster'] == settings.vmware.cluster + assert provider_content['annotation_notes'] == annotation_notes + assert provider_content['virtual_hw_version'] == virtual_hw_version + if not is_open('BZ:2266672'): + assert values['provider_content']['firmware'] == firmware + assert provider_content['resource_pool'] == resource_pool + assert provider_content['folder'] == folder + assert provider_content['memory_hot_add'] == memory_hot_add + assert provider_content['cpu_hot_add'] == cpu_hot_add + assert provider_content['cdrom_drive'] == cdrom_drive assert ( - provided_controller_value['controller'] == expected_controller_value['controller'] + provider_content['storage'][0]['controller'] == VMWARE_CONSTANTS['scsicontroller'] ) - for disk_index, expected_disk_value in enumerate(expected_controller_value['disks']): - provided_disk_value = { - key: value - for key, value in provided_controller_value['disks'][disk_index].items() - if key in expected_disk_value - } - assert provided_disk_value == expected_disk_value + assert provider_content['storage'][0]['disks'][0]['size'] == disk_size + assert ( + provider_content['network_interfaces'][0]['nic_type'] + == VMWARE_CONSTANTS['network_interface_name'] + ) + assert provider_content['network_interfaces'][0]['network'] == network + session.computeresource.delete(cr_name) + assert not session.computeresource.search(cr_name) @pytest.mark.tier2 diff --git a/tests/foreman/ui/test_errata.py b/tests/foreman/ui/test_errata.py index 2495399be13..dadcaf6bef9 100644 --- a/tests/foreman/ui/test_errata.py +++ b/tests/foreman/ui/test_errata.py @@ -233,8 +233,8 @@ def registered_contenthost( Using SCA and global registration. :note: rhel_contenthost will be parameterized by rhel6 to 9, also -fips for all distros. - to use specific rhel version parameterized contenthost, use pytest.mark.rhel_ver_match() - for marking contenthost version(s) for tests using this fixture. + to use specific rhel version parameterized contenthost; + use `pytest.mark.rhel_ver_match()` to mark contenthost version(s) for tests using this fixture. :repos: pass as a parameterized request list of upstream URLs for custom repositories. @@ -245,7 +245,6 @@ def registered_contenthost( indirect=True, ) """ - # read indirect parameterization request, could be None try: repos = getattr(request, 'param', repos).copy() except AttributeError: @@ -655,10 +654,7 @@ def test_host_content_errata_tab_pagination( pf4_pagination = session.host_new.get_errata_pagination(_chost_name) assert (_read_page := pf4_pagination.read()) assert _read_page != _prior_pagination - # assert per_page and total_pages remained the same - assert pf4_pagination.current_per_page == 5 assert pf4_pagination.current_page == 1 - assert pf4_pagination.total_pages > 1 # total_items decreased by one item_count = pf4_pagination.total_items assert item_count == _prior_app_count - 1 @@ -726,14 +722,24 @@ def test_host_content_errata_tab_pagination( @pytest.mark.tier2 @pytest.mark.skipif((not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url') -def test_positive_list(session, function_org_with_parameter, lce, target_sat): +def test_positive_list( + module_sca_manifest_org, + function_org, + function_lce, + target_sat, + module_lce, + module_cv, + module_ak, + session, +): """View all errata in an Org :id: 71c7a054-a644-4c1e-b304-6bc34ea143f4 - :Setup: Errata synced on satellite server. - - :steps: Create two Orgs each having a product synced which contains errata. + :steps: + 1. Setup two separate organization fixtures, function and module scope. + 2. Create and sync separate repositories for each org. + 3. Go to UI > Content Types > Errata page. :expectedresults: Check that the errata belonging to one Org is not showing in the other. @@ -741,23 +747,51 @@ def test_positive_list(session, function_org_with_parameter, lce, target_sat): :customerscenario: true """ - org = function_org_with_parameter - rc = target_sat.cli_factory.RepositoryCollection( - repositories=[target_sat.cli_factory.YumRepository(settings.repos.yum_3.url)] + _org_module = module_sca_manifest_org + _org_function = function_org + module_cv = module_cv.read() # for module sca org + # create and sync repository, for module org's errata + target_sat.cli_factory.setup_org_for_a_custom_repo( + { + 'url': CUSTOM_REPO_URL, + 'organization-id': _org_module.id, + 'lifecycle-environment-id': module_lce.id, + 'activationkey-id': module_ak.id, + 'content-view-id': module_cv.id, + }, ) - rc.setup_content(org.id, lce.id) + # create and sync repository, for function org's errata + target_sat.cli_factory.setup_org_for_a_custom_repo( + { + 'url': CUSTOM_REPO_3_URL, + 'organization-id': _org_function.id, + 'lifecycle-environment-id': function_lce.id, + }, + ) + with session: + # View in module org + session.organization.select(org_name=_org_module.name) assert ( session.errata.search(CUSTOM_REPO_ERRATA_ID, applicable=False)[0]['Errata ID'] == CUSTOM_REPO_ERRATA_ID + ), f'Could not find expected errata: {CUSTOM_REPO_ERRATA_ID}, in module org: {_org_module.name}.' + + assert not session.errata.search(CUSTOM_REPO_3_ERRATA_ID, applicable=False), ( + f'Found function org ({_org_function.name}) errata: {CUSTOM_REPO_3_ERRATA_ID},' + f' in module org ({_org_module.name}) as well.' ) - assert not session.errata.search(settings.repos.yum_3.errata[5], applicable=False) - session.organization.select(org_name=org.name) + # View in function org + session.organization.select(org_name=_org_function.name) assert ( - session.errata.search(settings.repos.yum_3.errata[5], applicable=False)[0]['Errata ID'] - == settings.repos.yum_3.errata[5] + session.errata.search(CUSTOM_REPO_3_ERRATA_ID, applicable=False)[0]['Errata ID'] + == CUSTOM_REPO_3_ERRATA_ID + ), f'Could not find expected errata: {CUSTOM_REPO_3_ERRATA_ID}, in function org: {_org_function.name}.' + + assert not session.errata.search(CUSTOM_REPO_ERRATA_ID, applicable=False), ( + f'Found module org ({_org_module.name}) errata: {CUSTOM_REPO_ERRATA_ID},' + f' in function org ({_org_function.name}) as well.' ) - assert not session.errata.search(CUSTOM_REPO_ERRATA_ID, applicable=False) @pytest.mark.tier2 @@ -1157,59 +1191,109 @@ def test_positive_content_host_search_type(session, erratatype_vm): @pytest.mark.tier3 +@pytest.mark.rhel_ver_match('8') @pytest.mark.parametrize( - 'module_repos_collection_with_setup', - [ - { - 'distro': 'rhel7', - 'SatelliteToolsRepository': {}, - 'RHELAnsibleEngineRepository': {'cdn': True}, - 'YumRepository': {'url': settings.repos.yum_9.url}, - } - ], + 'registered_contenthost', + [[CUSTOM_REPO_URL]], indirect=True, ) -def test_positive_show_count_on_content_host_page( - session, module_org_with_parameter, erratatype_vm -): - """Available errata count displayed in Content hosts page +def test_positive_show_count_on_host_pages(session, module_org, registered_contenthost): + """Available errata by type displayed in New Host>Errata page, + and expected count by type in Legacy>Content hosts page. :id: 8575e282-d56e-41dc-80dd-f5f6224417cb :Setup: - 1. Errata synced on satellite server. - 2. Some content hosts are present. + 1. Errata synced on satellite server from custom repository. + 2. Registered host, subscribed to promoted CVV, with repo synced to appliable custom packages and erratum. + + :steps: - :steps: Go to Hosts -> Content Hosts. + 1. Go to Hosts -> All Hosts, and Legacy ContentHost -> Hosts. + 2. None of the erratum are installable. + 3. Install all outdated applicable packages via yum. + 4. Recalculate errata applicablity for host. + 5. All of the erratum are now installable, on both pages from step 1. - :expectedresults: The available errata count is displayed. + :expectedresults: + The available errata count is displayed and updates. + Displayed erratum match between the two content host pages. :BZ: 1484044, 1775427 :customerscenario: true """ - vm = erratatype_vm + vm = registered_contenthost hostname = vm.hostname + assert vm.subscribed + assert vm.execute('subscription-manager repos').status == 0 + assert vm.applicable_errata_count == 0 + with session: session.location.select(loc_name=DEFAULT_LOC) + # new host UI + new_host_values = session.host_new.search(hostname) + assert new_host_values[0]['Name'] == hostname + # None of the erratum are installable + for errata_type in ('Security', 'Bugfix', 'Enhancement'): + installable_errata = None + empty_table = False + try: + # exception will be raised in case of unfound element (dropdown or dropdown entry) + # if an exception was raised, the table is missing/empty (no errata). + installable_errata = session.host_new.get_errata_by_type( + entity_name=hostname, + type=errata_type, + )['content']['errata']['table'] + except Exception: + empty_table = True + assert ( + not installable_errata + ), f'Found some installable {errata_type} errata, when none were expected.' + assert empty_table + # legacy contenthost UI content_host_values = session.contenthost.search(hostname) assert content_host_values[0]['Name'] == hostname installable_errata = content_host_values[0]['Installable Updates']['errata'] - for errata_type in ('security', 'bug_fix', 'enhancement'): - assert int(installable_errata[errata_type]) == 0 + assert ( + int(installable_errata[errata_type]) == 0 + ), f'Found some installable {errata_type} errata, when none were expected.' + # install outdated packages, recalculate errata applicability pkgs = ' '.join(FAKE_9_YUM_OUTDATED_PACKAGES) assert vm.execute(f'yum install -y {pkgs}').status == 0 - + assert vm.execute('subscription-manager repos').status == 0 + assert vm.applicable_errata_count == 5 + + # new host UI (errata tab) + new_host_values = session.host_new.search(hostname) + assert new_host_values[0]['Name'] == hostname + # erratum are installable + security_errata = session.host_new.get_errata_by_type( + entity_name=hostname, + type='Security', + )['content']['errata']['table'] + assert len(security_errata) == FAKE_9_YUM_SECURITY_ERRATUM_COUNT + for errata_type in ('Bugfix', 'Enhancement'): + installable_errata = session.host_new.get_errata_by_type( + entity_name=hostname, + type=errata_type, + )['content']['errata']['table'] + assert ( + len(installable_errata) == 1 + ), f'Expected only one {errata_type} errata to be installable.' + # legacy contenthost UI content_host_values = session.contenthost.search(hostname) assert content_host_values[0]['Name'] == hostname installable_errata = content_host_values[0]['Installable Updates']['errata'] - + # erratum are installable assert int(installable_errata['security']) == FAKE_9_YUM_SECURITY_ERRATUM_COUNT for errata_type in ('bug_fix', 'enhancement'): - assert int(installable_errata[errata_type]) == 1 + assert ( + int(installable_errata[errata_type]) == 1 + ), f'Expected only one {errata_type} errata to be installable.' @pytest.mark.tier3 diff --git a/tests/foreman/ui/test_host.py b/tests/foreman/ui/test_host.py index bb4f39655a9..7fec6db1f7d 100644 --- a/tests/foreman/ui/test_host.py +++ b/tests/foreman/ui/test_host.py @@ -1092,43 +1092,6 @@ def test_positive_read_details_page_from_new_ui(session, host_ui_options): assert values['overview']['details']['details']['comment'] == 'Host with fake data' -@pytest.mark.tier4 -@pytest.mark.rhel_ver_match('8') -def test_rex_new_ui(session, target_sat, rex_contenthost): - """Run remote execution using the new host details page - - :id: ee625595-4995-43b2-9e6d-633c9b33ff93 - - :steps: - 1. Navigate to Overview tab - 2. Schedule a job - 3. Wait for the job to finish - 4. Job is visible in Recent jobs card - - :expectedresults: Remote execution succeeded and the job is visible on Recent jobs card on - Overview tab - """ - hostname = rex_contenthost.hostname - job_args = { - 'job_category': 'Commands', - 'job_template': 'Run Command - Script Default', - 'template_content.command': 'ls', - } - with session: - session.location.select(loc_name=DEFAULT_LOC) - session.host_new.schedule_job(hostname, job_args) - task_result = target_sat.wait_for_tasks( - search_query=(f'Remote action: Run ls on {hostname}'), - search_rate=2, - max_tries=30, - ) - task_status = target_sat.api.ForemanTask(id=task_result[0].id).poll() - assert task_status['result'] == 'success' - recent_jobs = session.host_new.get_details(hostname, "overview.recent_jobs")['overview'] - assert recent_jobs['recent_jobs']['finished']['table'][0]['column0'] == "Run ls" - assert recent_jobs['recent_jobs']['finished']['table'][0]['column2'] == "succeeded" - - @pytest.mark.tier4 def test_positive_manage_table_columns( target_sat, test_name, ui_hosts_columns_user, current_sat_org, current_sat_location diff --git a/tests/foreman/ui/test_http_proxy.py b/tests/foreman/ui/test_http_proxy.py index 9e3f1e5b10b..10d2249be30 100644 --- a/tests/foreman/ui/test_http_proxy.py +++ b/tests/foreman/ui/test_http_proxy.py @@ -11,11 +11,23 @@ :CaseAutomation: Automated """ +from box import Box from fauxfactory import gen_integer, gen_string, gen_url import pytest from robottelo.config import settings -from robottelo.constants import DOCKER_REPO_UPSTREAM_NAME, REPO_TYPE +from robottelo.constants import DOCKER_REPO_UPSTREAM_NAME, REPO_TYPE, REPOS +from robottelo.hosts import ProxyHostError + + +@pytest.fixture +def function_spec_char_user(target_sat, session_auth_proxy): + """Creates a user with special character password on the auth HTTP proxy""" + name = gen_string('alpha').lower() # lower! + passwd = gen_string('punctuation').replace("'", '') + session_auth_proxy.add_user(name, passwd) + yield Box(name=name, passwd=passwd) + session_auth_proxy.remove_user(name) @pytest.mark.tier2 @@ -26,8 +38,6 @@ def test_positive_create_update_delete(module_org, module_location, target_sat): :id: 0c7cdf3d-778f-427a-9a2f-42ad7c23aa15 :expectedresults: All expected CRUD actions finished successfully - - :CaseImportance: High """ http_proxy_name = gen_string('alpha', 15) updated_proxy_name = gen_string('alpha', 15) @@ -53,8 +63,8 @@ def test_positive_create_update_delete(module_org, module_location, target_sat): assert http_proxy_values['http_proxy']['name'] == http_proxy_name assert http_proxy_values['http_proxy']['url'] == http_proxy_url assert http_proxy_values['http_proxy']['username'] == username - assert http_proxy_values['locations']['resources']['assigned'][0] == module_location.name - assert http_proxy_values['organizations']['resources']['assigned'][0] == module_org.name + assert module_location.name in http_proxy_values['locations']['resources']['assigned'] + assert module_org.name in http_proxy_values['organizations']['resources']['assigned'] # Update http_proxy with new name session.http_proxy.update(http_proxy_name, {'http_proxy.name': updated_proxy_name}) assert session.http_proxy.search(updated_proxy_name)[0]['Name'] == updated_proxy_name @@ -198,7 +208,7 @@ def test_set_default_http_proxy(module_org, module_location, setting_update, tar :steps: 1. Navigate to Infrastructure > Http Proxies 2. Create a Http Proxy - 3. GoTo to Administer > Settings > content tab + 3. Go to Administer > Settings > Content tab 4. Update the "Default HTTP Proxy" with created above. 5. Update "Default HTTP Proxy" to "no global default". @@ -239,29 +249,30 @@ def test_set_default_http_proxy(module_org, module_location, setting_update, tar def test_check_http_proxy_value_repository_details( function_org, function_location, function_product, setting_update, target_sat ): - """Deleted Global Http Proxy is reflected in repository details page". + """Global Http Proxy is reflected in repository details page". :id: 3f64255a-ef6c-4acb-b99b-e5579133b564 :steps: 1. Create Http Proxy (Go to Infrastructure > Http Proxies > New Http Proxy) - 2. GoTo to Administer > Settings > content tab + 2. Go to Administer > Settings > Content tab 3. Update the "Default HTTP Proxy" with created above. - 4. Create repository with Global Default Http Proxy. - 5. Delete the Http Proxy + 4. Create repository, check the Global Default Http Proxy is used. + 5. Delete the Http Proxy. + 6. Check it no longer appears on the Settings and repository page. :BZ: 1820193 :parametrized: yes :expectedresults: - 1. After deletion of "Default Http Proxy" its field on settings page should be - set to no global defult - 2. "HTTP Proxy" field in repository details page should be set to Global Default (None). + 1. Repository is automatically created with relevant Global Default Http Proxy. + 2. After Http Proxy deletion + - its field on Settings page should be set to Empty. + - "HTTP Proxy" field in repository details page should be set to Global Default (None). :CaseImportance: Medium """ - property_name = setting_update.name repo_name = gen_string('alpha') http_proxy_a = target_sat.api.HTTPProxy( @@ -285,45 +296,102 @@ def test_check_http_proxy_value_repository_details( 'repo_content.upstream_url': settings.repos.yum_0.url, }, ) + repo_values = session.repository.read(function_product.name, repo_name) + assert ( + repo_values['repo_content']['http_proxy_policy'] + == f'Global Default ({http_proxy_a.name})' + ) + session.http_proxy.delete(http_proxy_a.name) result = session.settings.read(f'name = {property_name}') assert result['table'][0]['Value'] == "Empty" - session.repository.search(function_product.name, repo_name)[0]['Name'] repo_values = session.repository.read(function_product.name, repo_name) assert repo_values['repo_content']['http_proxy_policy'] == 'Global Default (None)' @pytest.mark.tier3 @pytest.mark.run_in_one_thread -@pytest.mark.stubbed -def test_http_proxy_containing_special_characters(): +def test_http_proxy_containing_special_characters( + request, + target_sat, + session_auth_proxy, + function_spec_char_user, + module_sca_manifest_org, + default_location, +): """Test Manifest refresh and redhat repository sync with http proxy special characters in password. :id: 16082c6a-9320-4a9a-bd6c-5687b099c940 - :customerscenario: true + :setup: + 1. Have an authenticated HTTP proxy. + 2. At the Proxy side create a user with special characters in password + (via function_spec_user fixture), let's call him the spec-char user. :steps: - 1. Navigate to Infrastructure > Http Proxies - 2. Create HTTP Proxy with special characters in password. - 3. Go To to Administer > Settings > content tab - 4. Fill the details related to HTTP Proxy and click on "Test connection" button. - 5. Update the "Default HTTP Proxy" with created above. - 6. Refresh manifest. - 7. Enable and sync any redhat repositories. - - :BZ: 1844840 + 1. Check that no logs exist for the spec-char user at the proxy side yet. + 2. Create a proxy via UI using the spec-char user. + 3. Update settings to use the proxy for the content ops. + 4. Refresh the manifest, check it went through the proxy. + 5. Enable and sync some RH repository, check it went through the proxy. :expectedresults: - 1. "Test connection" button workes as expected. - 2. Manifest refresh, repository enable/disable and repository sync operation - finished successfully. + 1. HTTP proxy can be created via UI using the spec-char user. + 2. Manifest refresh, repository enable and sync succeed and are performed + through the HTTP proxy. - :CaseAutomation: NotAutomated + :BZ: 1844840 - :CaseImportance: High + :customerscenario: true """ + # Check that no logs exist for the spec-char user at the proxy side yet. + with pytest.raises(ProxyHostError): + session_auth_proxy.get_log(tail=100, grep=function_spec_char_user.name) + + # Create a proxy via UI using the spec-char user. + proxy_name = gen_string('alpha') + with target_sat.ui_session() as session: + session.organization.select(org_name=module_sca_manifest_org.name) + session.http_proxy.create( + { + 'http_proxy.name': proxy_name, + 'http_proxy.url': settings.http_proxy.auth_proxy_url, + 'http_proxy.username': function_spec_char_user.name, + 'http_proxy.password': function_spec_char_user.passwd, + 'locations.resources.assigned': [default_location.name], + 'organizations.resources.assigned': [module_sca_manifest_org.name], + } + ) + request.addfinalizer( + lambda: target_sat.api.HTTPProxy() + .search(query={'search': f'name={proxy_name}'})[0] + .delete() + ) + + # Update settings to use the proxy for the content ops. + session.settings.update( + 'name = content_default_http_proxy', + f'{proxy_name} ({settings.http_proxy.auth_proxy_url})', + ) + + # Refresh the manifest, check it went through the proxy. + target_sat.cli.Subscription.refresh_manifest( + {'organization-id': module_sca_manifest_org.id} + ) + assert session_auth_proxy.get_log( + tail=100, grep=f'CONNECT subscription.rhsm.redhat.com.*{function_spec_char_user.name}' + ), 'RHSM connection not found in proxy log' + + # Enable and sync some RH repository, check it went through the proxy. + repo_id = target_sat.api_factory.enable_sync_redhat_repo( + REPOS['rhae2'], module_sca_manifest_org.id + ) + repo = target_sat.api.Repository(id=repo_id).read() + assert session_auth_proxy.get_log( + tail=100, grep=f'CONNECT cdn.redhat.com.*{function_spec_char_user.name}' + ), 'CDN connection not found in proxy log' + assert repo.content_counts['rpm'] > 0, 'Where is my content?!' @pytest.mark.tier2 diff --git a/tests/foreman/ui/test_jobinvocation.py b/tests/foreman/ui/test_jobinvocation.py deleted file mode 100644 index 0394d00a888..00000000000 --- a/tests/foreman/ui/test_jobinvocation.py +++ /dev/null @@ -1,245 +0,0 @@ -"""Test class for Job Invocation procedure - -:Requirement: JobInvocation - -:CaseAutomation: Automated - -:CaseComponent: RemoteExecution - -:Team: Endeavour - -:CaseImportance: High - -""" -from collections import OrderedDict - -from inflection import camelize -import pytest - -from robottelo.utils.datafactory import ( - gen_string, - valid_hostgroups_list_short, -) - - -@pytest.fixture -def module_rhel_client_by_ip(module_org, smart_proxy_location, rhel7_contenthost, target_sat): - """Setup a broker rhel client to be used in remote execution by ip""" - rhel7_contenthost.configure_rex(satellite=target_sat, org=module_org) - target_sat.api_factory.update_vm_host_location( - rhel7_contenthost, location_id=smart_proxy_location.id - ) - return rhel7_contenthost - - -@pytest.mark.tier4 -def test_positive_hostgroups_full_nested_names( - module_org, - smart_proxy_location, - target_sat, -): - """Check that full host group names are displayed when invoking a job - - :id: 2301cd1d-ed82-4168-9f9b-d1661ac8fc5b - - :steps: - - 1. Go to Monitor -> Jobs -> Run job - 2. In "Target hosts and inputs" step, choose "Host groups" targeting - - :expectedresults: Verify that in the dropdown, full hostgroup names are present, e.g. Parent/Child/Grandchild - - :parametrized: yes - - :customerscenario: true - - :BZ: 2209968 - """ - names = valid_hostgroups_list_short() - tree = OrderedDict( - { - 'parent1': {'name': names[0], 'parent': None}, - 'parent2': {'name': names[1], 'parent': None}, - 'child1a': {'name': names[2], 'parent': 'parent1'}, - 'child1b': {'name': names[3], 'parent': 'parent1'}, - 'child2': {'name': names[4], 'parent': 'parent2'}, - 'grandchild1a1': {'name': names[5], 'parent': 'child1a'}, - 'grandchild1a2': {'name': names[6], 'parent': 'child1a'}, - 'grandchild1b': {'name': names[7], 'parent': 'child1b'}, - } - ) - expected_names = [] - for identifier, data in tree.items(): - name = data['name'] - parent_name = None if data['parent'] is None else tree[data['parent']]['name'] - target_sat.cli_factory.hostgroup( - { - 'name': name, - 'parent': parent_name, - 'organization-ids': module_org.id, - 'location-ids': smart_proxy_location.id, - } - ) - expected_name = '' - current = identifier - while current: - expected_name = ( - f"{tree[current]['name']}/{expected_name}" - if expected_name - else tree[current]['name'] - ) - current = tree[current]['parent'] - # we should have something like "parent1/child1a" - expected_names.append(expected_name) - - with target_sat.ui_session() as session: - session.organization.select(module_org.name) - session.location.select(smart_proxy_location.name) - hostgroups = session.jobinvocation.read_hostgroups() - - for name in expected_names: - assert name in hostgroups - - -@pytest.mark.tier4 -def test_positive_run_default_job_template_by_ip( - session, module_org, smart_proxy_location, module_rhel_client_by_ip -): - """Run a job template on a host connected by ip - - :id: 9a90aa9a-00b4-460e-b7e6-250360ee8e4d - - :Setup: Use pre-defined job template. - - :steps: - - 1. Set remote_execution_connect_by_ip on host to true - 2. Navigate to an individual host and click Run Job - 3. Select the job and appropriate template - 4. Run the job - - :expectedresults: Verify the job was successfully ran against the host - - :parametrized: yes - """ - hostname = module_rhel_client_by_ip.hostname - with session: - session.organization.select(module_org.name) - session.location.select(smart_proxy_location.name) - assert session.host.search(hostname)[0]['Name'] == hostname - session.jobinvocation.run( - { - 'job_category': 'Commands', - 'job_template': 'Run Command - Script Default', - 'search_query': f'name ^ {hostname}', - 'template_content.command': 'ls', - } - ) - session.jobinvocation.wait_job_invocation_state(entity_name='Run ls', host_name=hostname) - status = session.jobinvocation.read(entity_name='Run ls', host_name=hostname) - assert status['overview']['hosts_table'][0]['Status'] == 'success' - - -@pytest.mark.tier4 -def test_positive_run_custom_job_template_by_ip( - session, module_org, smart_proxy_location, module_rhel_client_by_ip -): - """Run a job template on a host connected by ip - - :id: e283ae09-8b14-4ce1-9a76-c1bbd511d58c - - :Setup: Create a working job template. - - :steps: - - 1. Set remote_execution_connect_by_ip on host to true - 2. Navigate to an individual host and click Run Job - 3. Select the job and appropriate template - 4. Run the job - - :expectedresults: Verify the job was successfully ran against the host - - :parametrized: yes - """ - hostname = module_rhel_client_by_ip.hostname - job_template_name = gen_string('alpha') - with session: - session.organization.select(module_org.name) - session.location.select(smart_proxy_location.name) - assert session.host.search(hostname)[0]['Name'] == hostname - session.jobtemplate.create( - { - 'template.name': job_template_name, - 'template.template_editor.rendering_options': 'Editor', - 'template.template_editor.editor': '<%= input("command") %>', - 'job.provider_type': 'Script', - 'inputs': [{'name': 'command', 'required': True, 'input_type': 'User input'}], - } - ) - assert session.jobtemplate.search(job_template_name)[0]['Name'] == job_template_name - session.jobinvocation.run( - { - 'job_category': 'Miscellaneous', - 'job_template': job_template_name, - 'search_query': f'name ^ {hostname}', - 'template_content.command': 'ls', - } - ) - job_description = f'{camelize(job_template_name.lower())} with inputs command="ls"' - session.jobinvocation.wait_job_invocation_state( - entity_name=job_description, host_name=hostname - ) - status = session.jobinvocation.read(entity_name=job_description, host_name=hostname) - assert status['overview']['hosts_table'][0]['Status'] == 'success' - - -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_schedule_recurring_host_job(self): - """Using the new Host UI, schedule a recurring job on a Host - - :id: 5052be04-28ab-4349-8bee-851ef76e4ffa - - :caseComponent: Ansible-RemoteExecution - - :Team: Rocket - - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Assign a role to host. - 4. Navigate to the new UI for the given Host. - 5. Select the Jobs subtab. - 6. Click the Schedule Recurring Job button, and using the popup, schedule a - recurring Job. - 7. Navigate to Job Invocations. - - :expectedresults: The scheduled Job appears in the Job Invocation list at the appointed - time - """ - - -@pytest.mark.stubbed -@pytest.mark.tier2 -def test_positive_schedule_recurring_hostgroup_job(self): - """Using the new recurring job scheduler, schedule a recurring job on a Hostgroup - - :id: c65db99b-11fe-4a32-89d0-0a4692b07efe - - :caseComponent: Ansible-RemoteExecution - - :Team: Rocket - - :steps: - 1. Register a RHEL host to Satellite. - 2. Import all roles available by default. - 3. Assign a role to host. - 4. Navigate to the Host Group page. - 5. Select the "Configure Ansible Job" action. - 6. Click the Schedule Recurring Job button, and using the popup, schedule a - recurring Job. - 7. Navigate to Job Invocations. - - :expectedresults: The scheduled Job appears in the Job Invocation list at the appointed - time - """ diff --git a/tests/foreman/ui/test_remoteexecution.py b/tests/foreman/ui/test_remoteexecution.py index cdfe9c7ec4e..bb82cd80ebf 100644 --- a/tests/foreman/ui/test_remoteexecution.py +++ b/tests/foreman/ui/test_remoteexecution.py @@ -1,4 +1,4 @@ -"""Test class for Remote Execution Management UI +"""Test class for Job Invocation procedure :Requirement: Remoteexecution @@ -11,20 +11,97 @@ :CaseImportance: High """ +from collections import OrderedDict import datetime import time +from inflection import camelize import pytest from wait_for import wait_for -from robottelo.utils.datafactory import gen_string +from robottelo.utils.datafactory import ( + gen_string, + valid_hostgroups_list_short, +) + + +@pytest.mark.tier4 +def test_positive_hostgroups_full_nested_names( + module_org, + smart_proxy_location, + target_sat, +): + """Check that full host group names are displayed when invoking a job + + :id: 2301cd1d-ed82-4168-9f9b-d1661ac8fc5b + + :steps: + + 1. Go to Monitor -> Jobs -> Run job + 2. In "Target hosts and inputs" step, choose "Host groups" targeting + + :expectedresults: Verify that in the dropdown, full hostgroup names are present, e.g. Parent/Child/Grandchild + + :parametrized: yes + + :customerscenario: true + + :BZ: 2209968 + """ + names = valid_hostgroups_list_short() + tree = OrderedDict( + { + 'parent1': {'name': names[0], 'parent': None}, + 'parent2': {'name': names[1], 'parent': None}, + 'child1a': {'name': names[2], 'parent': 'parent1'}, + 'child1b': {'name': names[3], 'parent': 'parent1'}, + 'child2': {'name': names[4], 'parent': 'parent2'}, + 'grandchild1a1': {'name': names[5], 'parent': 'child1a'}, + 'grandchild1a2': {'name': names[6], 'parent': 'child1a'}, + 'grandchild1b': {'name': names[7], 'parent': 'child1b'}, + } + ) + expected_names = [] + for identifier, data in tree.items(): + name = data['name'] + parent_name = None if data['parent'] is None else tree[data['parent']]['name'] + target_sat.cli_factory.hostgroup( + { + 'name': name, + 'parent': parent_name, + 'organization-ids': module_org.id, + 'location-ids': smart_proxy_location.id, + } + ) + expected_name = '' + current = identifier + while current: + expected_name = ( + f"{tree[current]['name']}/{expected_name}" + if expected_name + else tree[current]['name'] + ) + current = tree[current]['parent'] + # we should have something like "parent1/child1a" + expected_names.append(expected_name) + + with target_sat.ui_session() as session: + session.organization.select(module_org.name) + session.location.select(smart_proxy_location.name) + hostgroups = session.jobinvocation.read_hostgroups() + + for name in expected_names: + assert name in hostgroups -@pytest.mark.skip_if_open('BZ:2182353') @pytest.mark.rhel_ver_match('8') -@pytest.mark.tier3 -def test_positive_run_default_job_template_by_ip(session, rex_contenthost, module_org): - """Run a job template against a single host by ip +def test_positive_run_default_job_template( + session, + target_sat, + rex_contenthost, + module_org, +): + """Run a job template on a host :id: a21eac46-1a22-472d-b4ce-66097159a868 @@ -32,38 +109,38 @@ def test_positive_run_default_job_template_by_ip(session, rex_contenthost, modul :steps: - 1. Navigate to an individual host and click Run Job - 2. Select the job and appropriate template - 3. Run the job + 1. Get contenthost with rex enabled + 2. Navigate to an individual host and click Run Job + 3. Select the job and appropriate template + 4. Run the job - :expectedresults: Verify the job was successfully ran against the host + :expectedresults: Verify the job was successfully ran against the host, check also using the job widget on the main dashboard :parametrized: yes - :bz: 1898656 + :bz: 1898656, 2182353 :customerscenario: true """ + hostname = rex_contenthost.hostname - with session: + + with target_sat.ui_session() as session: session.organization.select(module_org.name) - session.location.select('Default Location') assert session.host.search(hostname)[0]['Name'] == hostname command = 'ls' - job_status = session.host.schedule_remote_job( - [hostname], + session.jobinvocation.run( { 'category_and_template.job_category': 'Commands', 'category_and_template.job_template': 'Run Command - Script Default', + 'target_hosts_and_inputs.targetting_type': 'Hosts', + 'target_hosts_and_inputs.targets': hostname, 'target_hosts_and_inputs.command': command, - 'advanced_fields.execution_order_randomized': True, - 'schedule.immediate': True, - }, + } ) - assert job_status['overview']['job_status'] == 'Success' - assert job_status['overview']['execution_order'] == 'Execution order: randomized' - assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'success' + session.jobinvocation.wait_job_invocation_state(entity_name='Run ls', host_name=hostname) + status = session.jobinvocation.read(entity_name='Run ls', host_name=hostname) + assert status['overview']['hosts_table'][0]['Status'] == 'success' # check status also on the job dashboard job_name = f'Run {command}' @@ -73,16 +150,54 @@ def test_positive_run_default_job_template_by_ip(session, rex_contenthost, modul assert job_name in [job['Name'] for job in success_jobs] -@pytest.mark.skip_if_open('BZ:2182353') +@pytest.mark.tier4 +@pytest.mark.rhel_ver_match('8') +def test_rex_through_host_details(session, target_sat, rex_contenthost, module_org): + """Run remote execution using the new host details page + + :id: ee625595-4995-43b2-9e6d-633c9b33ff93 + + :steps: + 1. Navigate to Overview tab + 2. Schedule a job + 3. Wait for the job to finish + 4. Job is visible in Recent jobs card + + :expectedresults: Remote execution succeeded and the job is visible on Recent jobs card on + Overview tab + """ + + hostname = rex_contenthost.hostname + + job_args = { + 'category_and_template.job_category': 'Commands', + 'category_and_template.job_template': 'Run Command - Script Default', + 'target_hosts_and_inputs.command': 'ls', + } + with target_sat.ui_session() as session: + session.organization.select(module_org.name) + session.host_new.schedule_job(hostname, job_args) + task_result = target_sat.wait_for_tasks( + search_query=(f'Remote action: Run ls on {hostname}'), + search_rate=2, + max_tries=30, + ) + task_status = target_sat.api.ForemanTask(id=task_result[0].id).poll() + assert task_status['result'] == 'success' + recent_jobs = session.host_new.get_details(hostname, "overview.recent_jobs")['overview'] + assert recent_jobs['recent_jobs']['finished']['table'][0]['column0'] == "Run ls" + assert recent_jobs['recent_jobs']['finished']['table'][0]['column2'] == "succeeded" + + +@pytest.mark.tier4 @pytest.mark.rhel_ver_match('8') -@pytest.mark.tier3 @pytest.mark.parametrize( 'ui_user', [{'admin': True}, {'admin': False}], indirect=True, ids=['adminuser', 'nonadminuser'] ) -def test_positive_run_custom_job_template_by_ip( - session, module_org, target_sat, default_location, ui_user, rex_contenthost +def test_positive_run_custom_job_template( + session, module_org, default_location, target_sat, ui_user, rex_contenthost ): - """Run a job template on a host connected by ip + """Run a job template on a host :id: 3a59eb15-67c4-46e1-ba5f-203496ec0b0c @@ -103,13 +218,14 @@ def test_positive_run_custom_job_template_by_ip( :customerscenario: true """ + + hostname = rex_contenthost.hostname ui_user.location.append(target_sat.api.Location(id=default_location.id)) ui_user.update(['location']) - hostname = rex_contenthost.hostname job_template_name = gen_string('alpha') - with session: + with target_sat.ui_session() as session: session.organization.select(module_org.name) - session.location.select('Default Location') + assert session.host.search(hostname)[0]['Name'] == hostname session.jobtemplate.create( { 'template.name': job_template_name, @@ -120,29 +236,29 @@ def test_positive_run_custom_job_template_by_ip( } ) assert session.jobtemplate.search(job_template_name)[0]['Name'] == job_template_name - assert session.host.search(hostname)[0]['Name'] == hostname - job_status = session.host.schedule_remote_job( - [hostname], + session.jobinvocation.run( { 'category_and_template.job_category': 'Miscellaneous', 'category_and_template.job_template': job_template_name, + 'target_hosts_and_inputs.targets': hostname, 'target_hosts_and_inputs.command': 'ls', - 'schedule.immediate': True, - }, + } ) - assert job_status['overview']['job_status'] == 'Success' - assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'success' + job_description = f'{camelize(job_template_name.lower())} with inputs command="ls"' + session.jobinvocation.wait_job_invocation_state( + entity_name=job_description, host_name=hostname + ) + status = session.jobinvocation.read(entity_name=job_description, host_name=hostname) + assert status['overview']['hosts_table'][0]['Status'] == 'success' -@pytest.mark.skip_if_open('BZ:2182353') @pytest.mark.upgrade @pytest.mark.tier3 @pytest.mark.rhel_ver_list([8]) -def test_positive_run_job_template_multiple_hosts_by_ip( - session, module_org, target_sat, registered_hosts +def test_positive_run_job_template_multiple_hosts( + session, module_org, target_sat, rex_contenthosts ): - """Run a job template against multiple hosts by ip + """Run a job template against multiple hosts :id: c4439ec0-bb80-47f6-bc31-fa7193bfbeeb @@ -158,22 +274,22 @@ def test_positive_run_job_template_multiple_hosts_by_ip( :expectedresults: Verify the job was successfully ran against the hosts """ + host_names = [] - for vm in registered_hosts: + for vm in rex_contenthosts: host_names.append(vm.hostname) vm.configure_rex(satellite=target_sat, org=module_org) - with session: + with target_sat.ui_session() as session: session.organization.select(module_org.name) - session.location.select('Default Location') - hosts = session.host.search(' or '.join([f'name="{hostname}"' for hostname in host_names])) - assert {host['Name'] for host in hosts} == set(host_names) + for host in host_names: + assert session.host.search(host)[0]['Name'] == host + session.host.reset_search() job_status = session.host.schedule_remote_job( host_names, { 'category_and_template.job_category': 'Commands', 'category_and_template.job_template': 'Run Command - Script Default', - 'target_hosts_and_inputs.command': 'ls', - 'schedule.immediate': True, + 'target_hosts_and_inputs.command': 'sleep 5', }, ) assert job_status['overview']['job_status'] == 'Success' @@ -185,7 +301,6 @@ def test_positive_run_job_template_multiple_hosts_by_ip( ) -@pytest.mark.skip_if_open('BZ:2182353') @pytest.mark.rhel_ver_match('8') @pytest.mark.tier3 def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_contenthost): @@ -211,19 +326,20 @@ def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_cont :parametrized: yes """ - job_time = 10 * 60 + job_time = 6 * 60 hostname = rex_contenthost.hostname with session: session.organization.select(module_org.name) session.location.select('Default Location') assert session.host.search(hostname)[0]['Name'] == hostname plan_time = session.browser.get_client_datetime() + datetime.timedelta(seconds=job_time) + command_to_run = 'sleep 10' job_status = session.host.schedule_remote_job( [hostname], { 'category_and_template.job_category': 'Commands', 'category_and_template.job_template': 'Run Command - Script Default', - 'target_hosts_and_inputs.command': 'ls', + 'target_hosts_and_inputs.command': command_to_run, 'schedule.future': True, 'schedule_future_execution.start_at_date': plan_time.strftime("%Y/%m/%d"), 'schedule_future_execution.start_at_time': plan_time.strftime("%H:%M"), @@ -237,34 +353,36 @@ def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_cont # the job_time must be significantly greater than job creation time. assert job_left_time > 0 assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'N/A' + assert job_status['overview']['hosts_table'][0]['Status'] in ('Awaiting start', 'N/A') # sleep 3/4 of the left time time.sleep(job_left_time * 3 / 4) - job_status = session.jobinvocation.read('Run ls', hostname, 'overview.hosts_table') + job_status = session.jobinvocation.read( + f'Run {command_to_run}', hostname, 'overview.hosts_table' + ) assert job_status['overview']['hosts_table'][0]['Host'] == hostname - assert job_status['overview']['hosts_table'][0]['Status'] == 'N/A' + assert job_status['overview']['hosts_table'][0]['Status'] in ('Awaiting start', 'N/A') # recalculate the job left time to be more accurate job_left_time = (plan_time - session.browser.get_client_datetime()).total_seconds() # the last read time should not take more than 1/4 of the last left time assert job_left_time > 0 wait_for( - lambda: session.jobinvocation.read('Run ls', hostname, 'overview.hosts_table')[ - 'overview' - ]['hosts_table'][0]['Status'] + lambda: session.jobinvocation.read( + f'Run {command_to_run}', hostname, 'overview.hosts_table' + )['overview']['hosts_table'][0]['Status'] == 'running', timeout=(job_left_time + 30), delay=1, ) # wait the job to change status to "success" wait_for( - lambda: session.jobinvocation.read('Run ls', hostname, 'overview.hosts_table')[ - 'overview' - ]['hosts_table'][0]['Status'] + lambda: session.jobinvocation.read( + f'Run {command_to_run}', hostname, 'overview.hosts_table' + )['overview']['hosts_table'][0]['Status'] == 'success', timeout=30, delay=1, ) - job_status = session.jobinvocation.read('Run ls', hostname, 'overview') + job_status = session.jobinvocation.read(f'Run {command_to_run}', hostname, 'overview') assert job_status['overview']['job_status'] == 'Success' assert job_status['overview']['hosts_table'][0]['Host'] == hostname assert job_status['overview']['hosts_table'][0]['Status'] == 'success' @@ -362,27 +480,6 @@ def test_positive_ansible_variables_imported_with_roles(session): """ -@pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_roles_import_in_background(session): - """Verify that importing roles does not create a popup that blocks the UI - - :id: 4f1c7b76-9c67-42b2-9a73-980ca1f05abc - - :steps: - - 1. Import Ansible roles - - :expectedresults: Verify that the UI is accessible while roles are importing - - :CaseAutomation: NotAutomated - - :CaseComponent: Ansible-ConfigurationManagement - - :Team: Rocket - """ - - @pytest.mark.stubbed @pytest.mark.tier3 def test_positive_ansible_roles_ignore_list(session): @@ -508,26 +605,52 @@ def test_positive_set_ansible_role_order_per_hostgroup(session): @pytest.mark.stubbed -@pytest.mark.tier3 -def test_positive_matcher_field_highlight(session): - """Verify that Ansible variable matcher fields change color when modified +@pytest.mark.tier2 +def test_positive_schedule_recurring_host_job(self): + """Using the new Host UI, schedule a recurring job on a Host - :id: 67b45cfe-31bb-41a8-b88e-27917c68f33e + :id: 5052be04-28ab-4349-8bee-851ef76e4ffa + + :caseComponent: Ansible-RemoteExecution + + :Team: Rocket :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Assign a role to host. + 4. Navigate to the new UI for the given Host. + 5. Select the Jobs subtab. + 6. Click the Schedule Recurring Job button, and using the popup, schedule a + recurring Job. + 7. Navigate to Job Invocations. + + :expectedresults: The scheduled Job appears in the Job Invocation list at the appointed + time + """ - 1. Navigate to Configure > Variables > $variablename - 2. Select the "Override" checkbox in the "Default Behavior" section - 3. Click "+Add Matcher" in the "Specify Matcher" section - 4. Select an option from the "Attribute type" dropdown - 5. Add text to the attribute type input field - 6. Add text to the "Value" input field - :expectedresults: The background of each field turns yellow when a change is made +@pytest.mark.stubbed +@pytest.mark.tier2 +def test_positive_schedule_recurring_hostgroup_job(self): + """Using the new recurring job scheduler, schedule a recurring job on a Hostgroup - :CaseAutomation: NotAutomated + :id: c65db99b-11fe-4a32-89d0-0a4692b07efe - :CaseComponent: Ansible-ConfigurationManagement + :caseComponent: Ansible-RemoteExecution :Team: Rocket + + :steps: + 1. Register a RHEL host to Satellite. + 2. Import all roles available by default. + 3. Assign a role to host. + 4. Navigate to the Host Group page. + 5. Select the "Configure Ansible Job" action. + 6. Click the Schedule Recurring Job button, and using the popup, schedule a + recurring Job. + 7. Navigate to Job Invocations. + + :expectedresults: The scheduled Job appears in the Job Invocation list at the appointed + time """ diff --git a/tests/foreman/ui/test_rhc.py b/tests/foreman/ui/test_rhc.py index f3ab5eae14f..df90a5768ca 100644 --- a/tests/foreman/ui/test_rhc.py +++ b/tests/foreman/ui/test_rhc.py @@ -6,7 +6,7 @@ :CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/ui/test_rhcloud_insights.py b/tests/foreman/ui/test_rhcloud_insights.py index 2a3cabf789f..3646658c2b0 100644 --- a/tests/foreman/ui/test_rhcloud_insights.py +++ b/tests/foreman/ui/test_rhcloud_insights.py @@ -6,7 +6,7 @@ :CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/foreman/ui/test_rhcloud_inventory.py b/tests/foreman/ui/test_rhcloud_inventory.py index 642bef3b84b..9c98ade0205 100644 --- a/tests/foreman/ui/test_rhcloud_inventory.py +++ b/tests/foreman/ui/test_rhcloud_inventory.py @@ -6,7 +6,7 @@ :CaseComponent: RHCloud -:Team: Platform +:Team: Phoenix-subscriptions :CaseImportance: High diff --git a/tests/upgrades/test_bookmarks.py b/tests/upgrades/test_bookmarks.py index b0d04cfa985..ff53bcdb30b 100644 --- a/tests/upgrades/test_bookmarks.py +++ b/tests/upgrades/test_bookmarks.py @@ -13,7 +13,7 @@ """ import pytest -from robottelo.constants import BOOKMARK_ENTITIES +from robottelo.constants import BOOKMARK_ENTITIES_SELECTION class TestPublicDisableBookmark: @@ -45,7 +45,7 @@ def test_pre_create_public_disable_bookmark(self, request, target_sat): :CaseImportance: Critical """ - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + request.node.name bm = target_sat.api.Bookmark( controller=entity['controller'], @@ -77,7 +77,7 @@ def test_post_create_public_disable_bookmark(self, dependent_scenario_name, targ :CaseImportance: Critical """ pre_test_name = dependent_scenario_name - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + pre_test_name bm = target_sat.api.Bookmark().search(query={'search': f'name="{book_mark_name}"'})[0] assert bm.controller == entity['controller'] @@ -115,7 +115,7 @@ def test_pre_create_public_enable_bookmark(self, request, target_sat): :customerscenario: true """ - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + request.node.name bm = target_sat.api.Bookmark( controller=entity['controller'], @@ -145,7 +145,7 @@ def test_post_create_public_enable_bookmark(self, dependent_scenario_name, targe :CaseImportance: Critical """ pre_test_name = dependent_scenario_name - for entity in BOOKMARK_ENTITIES: + for entity in BOOKMARK_ENTITIES_SELECTION: book_mark_name = entity["name"] + pre_test_name bm = target_sat.api.Bookmark().search(query={'search': f'name="{book_mark_name}"'})[0] assert bm.controller == entity['controller'] diff --git a/tests/upgrades/test_virtwho.py b/tests/upgrades/test_virtwho.py index 3691e3a8d70..95b36ec5749 100644 --- a/tests/upgrades/test_virtwho.py +++ b/tests/upgrades/test_virtwho.py @@ -37,7 +37,7 @@ def form_data(target_sat): 'satellite_url': target_sat.hostname, 'hypervisor_username': esx.hypervisor_username, 'hypervisor_password': esx.hypervisor_password, - 'name': 'preupgrade_virt_who', + 'name': f'preupgrade_virt_who_{gen_string("alpha")}', } @@ -120,6 +120,7 @@ def test_pre_create_virt_who_configuration( 'org_id': org.id, 'org_name': org.name, 'org_label': org.label, + 'name': vhd.name, } ) @@ -146,15 +147,16 @@ def test_post_crud_virt_who_configuration(self, form_data, pre_upgrade_data, tar org_id = pre_upgrade_data.get('org_id') org_name = pre_upgrade_data.get('org_name') org_label = pre_upgrade_data.get('org_label') + name = pre_upgrade_data.get('name') # Post upgrade, Verify virt-who exists and has same status. vhd = target_sat.api.VirtWhoConfig(organization_id=org_id).search( - query={'search': f'name={form_data["name"]}'} + query={'search': f'name={name}'} )[0] if not is_open('BZ:1802395'): assert vhd.status == 'ok' # Verify virt-who status via CLI as we cannot check it via API now - vhd_cli = target_sat.cli.VirtWhoConfig.exists(search=('name', form_data['name'])) + vhd_cli = target_sat.cli.VirtWhoConfig.exists(search=('name', name)) assert ( target_sat.cli.VirtWhoConfig.info({'id': vhd_cli['id']})['general-information'][ 'status' @@ -185,7 +187,7 @@ def test_post_crud_virt_who_configuration(self, form_data, pre_upgrade_data, tar ) virt_who_instance = ( target_sat.api.VirtWhoConfig(organization_id=org_id) - .search(query={'search': f'name={form_data["name"]}'})[0] + .search(query={'search': f'name={name}'})[0] .status ) assert virt_who_instance == 'ok'