Skip to content

fix test and include foremanctl in hosts and satellite_misxins - #22150

Open
nacoool wants to merge 1 commit into
SatelliteQE:masterfrom
nacoool:fix_installation_with_both_methods
Open

fix test and include foremanctl in hosts and satellite_misxins#22150
nacoool wants to merge 1 commit into
SatelliteQE:masterfrom
nacoool:fix_installation_with_both_methods

Conversation

@nacoool

@nacoool nacoool commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem Statement

Test test_installation_with_both_methods initially fails because of version issue.
Deeper investigation reveals multiple failures happen.
Some of them are because of failures related to foremanctl deploy.
some of them is because it is not customised to include foremanctl.

Solution

Fix Test test_installation_with_both_methods by customising it so it can use version, is_stream, is_upstream etc..
by including vars which we will use for foremanctl,
similar to is_foremanctl_available is_satellitectl_available is included.

Related Issues

Summary by Sourcery

Update Satellite installation assertions and host helpers to support satellitectl/foremanctl-based deployments and fix version/health detection for mixed installation methods.

Bug Fixes:

  • Adjust installation log and health checks to avoid false failures when using different installation methods and known open issues.
  • Fix upstream/downstream and stream detection on Capsule/Satellite hosts when satellitectl or foremanctl-based container deployments are present.

Enhancements:

  • Add satellitectl/foremanctl-aware RPM and version resolution for Capsule and Satellite hosts.
  • Introduce satellitectl availability checks and use foremanctl health when container-based control tools are installed.
  • Gate certain journald and log error checks behind known bug trackers to reduce noise from expected issues.

@nacoool
nacoool requested a review from a team as a code owner July 16, 2026 08:32
@sourcery-ai

sourcery-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR fixes the test_installation_with_both_methods by making installation assertions and host helpers aware of satellitectl/foremanctl-based deployments and gating certain log/health checks behind bug flags and availability checks.

Sequence diagram for updated installation health assertions

sequenceDiagram
participant SatelliteMixins
participant Host
participant Journald
participant foremanctl
participant SatelliteCLI

SatelliteMixins->>Host: assert_install_assertions()
SatelliteMixins->>Host: is_open(SAT-47736)
alt SAT-47736 not open
  SatelliteMixins->>Host: get_service_names()
  SatelliteMixins->>Journald: execute(journalctl --grep ERROR service_units)
  SatelliteMixins->>Host: execute(grep -iR error /var/log/httpd/*)
  SatelliteMixins->>Host: execute(grep -iR error /var/log/candlepin/*)
  SatelliteMixins->>Journald: execute(journalctl --unit=httpd)
end

SatelliteMixins->>Host: is_satellitectl_available()
alt satellitectl available
  SatelliteMixins->>foremanctl: execute(foremanctl health)
else satellitectl not available
  SatelliteMixins->>SatelliteCLI: cli.Health.check()
end
Loading

File-Level Changes

Change Details Files
Make installation assertions conditional on install method and known bug SAT-47736, and add satellitectl/foremanctl-aware health checks.
  • Move foreman production log check inside the satellite-installer-only branch and keep it gated by SAT-21086.
  • Wrap journald/httpd/candlepin and httpd journal checks behind SAT-47736 bug flag and simplify httpd error handling to a strict no-error condition.
  • Add conditional health verification using foremanctl health when satellitectl/foremanctl is available, falling back to existing CLI Health.check when not.
robottelo/host_helpers/satellite_mixins.py
Extend Capsule/Satellite host helpers to understand satellitectl/foremanctl RPMs and commands for upstream/downstream/stream detection and installation.
  • Add container RPM and upstream container identifiers for Capsule and Satellite host classes.
  • Update is_upstream to also consider the presence of the container RPM when determining upstream vs downstream.
  • Update is_stream to treat satellitectl presence as a stream indicator in addition to the RPM release check.
  • Adjust version resolution to choose between traditional RPMs and container RPMs based on satellitectl/foremanctl availability.
  • Introduce is_satellitectl_available helper mirroring is_foremanctl_available for detecting satellitectl on the system.
  • Change install_satellite_foremanctl to install satellitectl instead of foremanctl while keeping the rest of the flow intact.
robottelo/hosts.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • In is_upstream, the combined status expression (rpm -q product_rpm_name).status and (rpm -q container_rpm_name).status != 0 will evaluate to False when the product RPM is missing but the container RPM is present; if the intent is to treat either downstream RPM as making the host non-upstream, you likely want both checks explicitly != 0 rather than chained with and.
  • In assert_install_assertions, when using foremanctl health the assertion checks 'failed=0' in result instead of result.stdout, which will always be False and should be updated to inspect the command output string.
  • The new journalctl and httpd/candlepin log checks under SAT-47736 are stricter (no filtering of expected transient httpd errors and --grep ERROR instead of --priority err), which may introduce flakiness; consider preserving the previous transient error filters or documenting why these are safe to tighten.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `is_upstream`, the combined status expression `(rpm -q product_rpm_name).status and (rpm -q container_rpm_name).status != 0` will evaluate to False when the product RPM is missing but the container RPM is present; if the intent is to treat either downstream RPM as making the host non-upstream, you likely want both checks explicitly `!= 0` rather than chained with `and`.
- In `assert_install_assertions`, when using `foremanctl health` the assertion checks `'failed=0' in result` instead of `result.stdout`, which will always be False and should be updated to inspect the command output string.
- The new `journalctl` and httpd/candlepin log checks under `SAT-47736` are stricter (no filtering of expected transient httpd errors and `--grep ERROR` instead of `--priority err`), which may introduce flakiness; consider preserving the previous transient error filters or documenting why these are safe to tighten.

## Individual Comments

### Comment 1
<location path="robottelo/host_helpers/satellite_mixins.py" line_range="583-585" />
<code_context>
+            httpd_log = self.execute('journalctl --unit=httpd')
+            assert 'WARNING' not in httpd_log.stdout
+
+        if self.is_satellitectl_available():
+            result = self.execute('foremanctl health')
+            assert 'failed=0' in result
+        else:
+            result = self.cli.Health.check()
</code_context>
<issue_to_address>
**issue (bug_risk):** Use the command output when checking foremanctl health result.

`execute()` returns an object whose output is in `stdout`, as shown in the `httpd_log` assertion above. Here, the assertion checks `'failed=0' in result` instead of `result.stdout`, which will not correctly validate the command output and may always fail or raise. Please update this to inspect `result.stdout` (e.g. `assert 'failed=0' in result.stdout`).
</issue_to_address>

### Comment 2
<location path="robottelo/host_helpers/satellite_mixins.py" line_range="570-572" />
<code_context>
             )
             assert not result.stdout

-        # Check httpd logs, filtering expected transient startup errors
-        # (httpd may start before Foreman/containers are ready, causing brief "Connection refused")
-        result = self.execute(r'grep -iR "error" /var/log/httpd/*')
-        if result.stdout:
-            filtered_errors = [
</code_context>
<issue_to_address>
**suggestion:** Restored httpd error check no longer filters known transient/expected errors.

The prior version excluded known transient startup errors (e.g., connection refused to 127.0.0.1:3000, temporary backend failures). The updated code now fails on any `error` match, which can make tests flaky when those benign cases occur. Please restore targeted filtering so the assertion only fails on unexpected errors.

Suggested implementation:

```python
            # Check foreman-installer log
            result = self.execute(
                r'grep "\[ERROR" --context=100 /var/log/foreman-installer/satellite.log'
            )
            assert not result.stdout

            # Check httpd logs, filtering expected transient startup errors
            # (httpd may start before Foreman/containers are ready, causing brief "Connection refused")
            result = self.execute(r'grep -iR "error" /var/log/httpd/*')
            if result.stdout:
                lines = result.stdout.splitlines()
                filtered_errors = [
                    line
                    for line in lines
                    if not (
                        # Known transient startup errors / benign conditions:
                        "Connection refused" in line
                        or "connect() failed" in line
                        or "127.0.0.1:3000" in line
                        or "127.0.0.1:6000" in line
                        or "Temporary failure in name resolution" in line
                        or "proxy: error reading status line from remote server" in line
                        or "AH01102" in line  # proxy: error reading status line from remote server
                        or "AH00959" in line  # failed to make connection to backend
                    )
                ]
                # Only fail if there are unexpected errors left after filtering
                assert not "\n".join(filtered_errors)

```

You may want to refine the filtered error patterns to exactly match the previously accepted transient httpd errors in your codebase or logs (e.g., adjust IP/port combinations or Apache error codes). If there are helper utilities or constants already defined for log filtering elsewhere in robottelo, consider reusing them here instead of hardcoding the patterns.
</issue_to_address>

### Comment 3
<location path="robottelo/hosts.py" line_range="1748-1750" />
<code_context>
         :rtype: bool
         """
-        return self.execute(f'rpm -q {self.product_rpm_name}').status != 0
+        return (
+            self.execute(f'rpm -q {self.product_rpm_name}').status
+            and self.execute(f'rpm -q {self.container_rpm_name}').status
+        ) != 0

</code_context>
<issue_to_address>
**suggestion:** Simplify and clarify upstream detection logic to avoid subtle `and`/int behavior.

The current check depends on `and` returning an integer and on rpm exit codes being strictly 0/non‑0, which is easy to misread. Please rewrite the condition to compare each status explicitly (e.g., check each `.status != 0` and combine them) so the “both RPMs missing means upstream” intent is immediately clear.
</issue_to_address>

### Comment 4
<location path="robottelo/hosts.py" line_range="1809-1818" />
<code_context>
         result = self.execute('which foremanctl')
         return result.status == 0

+    def is_satellitectl_available(self):
+        """Check if satellitectl is installed on the system.
+
+        Only checks if the command exists, not if the package is available in repos.
+        This ensures auto-detection defaults to satellite-installer on clean systems.
+
+        :return: True if satellite command is installed
+        :rtype: bool
+        """
+        # Check if foremanctl command exists (already installed)
+        result = self.execute('which satellitectl')
+        return result.status == 0
</code_context>
<issue_to_address>
**nitpick:** Align the inline comment with the satellitectl check to avoid confusion.

The inline comment still refers to `foremanctl` while this method checks `satellitectl`. Please update the comment to mention `satellitectl` to clearly distinguish it from `is_foremanctl_available`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +583 to +585
if self.is_satellitectl_available():
result = self.execute('foremanctl health')
assert 'failed=0' in result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Use the command output when checking foremanctl health result.

execute() returns an object whose output is in stdout, as shown in the httpd_log assertion above. Here, the assertion checks 'failed=0' in result instead of result.stdout, which will not correctly validate the command output and may always fail or raise. Please update this to inspect result.stdout (e.g. assert 'failed=0' in result.stdout).

Comment on lines +570 to +572
# Check httpd logs, filtering expected transient startup errors
# (httpd may start before Foreman/containers are ready, causing brief "Connection refused")
result = self.execute(r'grep -iR "error" /var/log/httpd/*')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Restored httpd error check no longer filters known transient/expected errors.

The prior version excluded known transient startup errors (e.g., connection refused to 127.0.0.1:3000, temporary backend failures). The updated code now fails on any error match, which can make tests flaky when those benign cases occur. Please restore targeted filtering so the assertion only fails on unexpected errors.

Suggested implementation:

            # Check foreman-installer log
            result = self.execute(
                r'grep "\[ERROR" --context=100 /var/log/foreman-installer/satellite.log'
            )
            assert not result.stdout

            # Check httpd logs, filtering expected transient startup errors
            # (httpd may start before Foreman/containers are ready, causing brief "Connection refused")
            result = self.execute(r'grep -iR "error" /var/log/httpd/*')
            if result.stdout:
                lines = result.stdout.splitlines()
                filtered_errors = [
                    line
                    for line in lines
                    if not (
                        # Known transient startup errors / benign conditions:
                        "Connection refused" in line
                        or "connect() failed" in line
                        or "127.0.0.1:3000" in line
                        or "127.0.0.1:6000" in line
                        or "Temporary failure in name resolution" in line
                        or "proxy: error reading status line from remote server" in line
                        or "AH01102" in line  # proxy: error reading status line from remote server
                        or "AH00959" in line  # failed to make connection to backend
                    )
                ]
                # Only fail if there are unexpected errors left after filtering
                assert not "\n".join(filtered_errors)

You may want to refine the filtered error patterns to exactly match the previously accepted transient httpd errors in your codebase or logs (e.g., adjust IP/port combinations or Apache error codes). If there are helper utilities or constants already defined for log filtering elsewhere in robottelo, consider reusing them here instead of hardcoding the patterns.

Comment thread robottelo/hosts.py
Comment on lines +1748 to +1750
return (
self.execute(f'rpm -q {self.product_rpm_name}').status
and self.execute(f'rpm -q {self.container_rpm_name}').status

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Simplify and clarify upstream detection logic to avoid subtle and/int behavior.

The current check depends on and returning an integer and on rpm exit codes being strictly 0/non‑0, which is easy to misread. Please rewrite the condition to compare each status explicitly (e.g., check each .status != 0 and combine them) so the “both RPMs missing means upstream” intent is immediately clear.

Comment thread robottelo/hosts.py
Comment on lines +1809 to +1818
def is_satellitectl_available(self):
"""Check if satellitectl is installed on the system.

Only checks if the command exists, not if the package is available in repos.
This ensures auto-detection defaults to satellite-installer on clean systems.

:return: True if satellite command is installed
:rtype: bool
"""
# Check if foremanctl command exists (already installed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Align the inline comment with the satellitectl check to avoid confusion.

The inline comment still refers to foremanctl while this method checks satellitectl. Please update the comment to mention satellitectl to clearly distinguish it from is_foremanctl_available.

@nacoool
nacoool requested review from a team July 16, 2026 10:09
Comment thread robottelo/hosts.py
self.upstream_container if self.is_foremanctl_available() else self.upstream_rpm_name
)
rpm_name = upstream_rpm_name if self.is_upstream else product_rpm_name
return self.execute(f'rpm -q --qf "%{{VERSION}}" {rpm_name}').stdout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already did rpm -q when invoked is_upstream. Instead of calling the same command twice, you can call it once and set the version as a byproduct. I mean the execution should look like this:

# run once in the initialization: 

# Check for satellitectl
downstream_containerized_command = self.execute(f'rpm -q --qf "%{{VERSION}}" satellitectl')
upstream_containerized_command = self.execute(f'rpm -q --qf "%{{VERSION}}" foremanctl')
downstream_rpm_command = self.execute(f'rpm -q --qf "%{{VERSION}}" {product_rpm_name}')
upstream_rpm_command = self.execute(f'rpm -q --qf "%{{VERSION}}" {upstream_rpm_name}')

is_upstream = upstream_containerized_command.status == 0 || upstream_rpm_command.status == 0
is_containerized = upstream_containerized_command.status == 0 || downstream_containerized_command.status == 0
is_rpm = ! is_containerized
version = downstream_containerized_command.stdout || downstream_rpm_command.stdout || upstream_containerized_command.stdout || upstream_rpm_command.stdout 

This should give you all the indications that you need for later use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have multiple tests which uses version function, it would break them.

@nacoool nacoool added enhancement An addition to the robottelo framework Framework Changes A modification of the robottelo framework No-CherryPick PR doesnt need CherryPick to previous branches Critical Severity labels Jul 21, 2026
@nacoool
nacoool force-pushed the fix_installation_with_both_methods branch from 263eea7 to 570b7b6 Compare July 21, 2026 05:31
@nacoool

nacoool commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author
trigger: test-robottelo
pytest: tests/foreman/installer/test_installer.py -k "test_installation_with_both_methods[foremanctl-method]"

@nacoool

nacoool commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author
trigger: test-robottelo
pytest: tests/foreman/installer/test_installer.py -k "test_installation_with_both_methods[foremanctl-method]"
deployment_type: container

@Satellite-QE

Copy link
Copy Markdown
Collaborator

PRT Result

Build Number: 16177
Build Status: UNSTABLE
PRT Comment: pytest tests/foreman/installer/test_installer.py -k "test_installation_with_both_methods[foremanctl-method]" --external-logging
Test Result : ========== 22 deselected, 24 warnings, 1 error in 4624.81s (1:17:04) ===========

@Satellite-QE Satellite-QE added the PRT-Failed Indicates that latest PRT run is failed for the PR label Jul 21, 2026
@nacoool

nacoool commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author
trigger: test-robottelo
pytest: tests/foreman/installer/test_installer.py -k "test_installation_with_both_methods[foremanctl-method]"
deployment_type: container

@Satellite-QE

Copy link
Copy Markdown
Collaborator

PRT Result

Build Number: 16236
Build Status: UNSTABLE
PRT Comment: pytest tests/foreman/installer/test_installer.py -k "test_installation_with_both_methods[foremanctl-method]" --external-logging
Test Result : ========== 1 failed, 21 deselected, 9 warnings in 1822.65s (0:30:22) ===========

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Critical Severity enhancement An addition to the robottelo framework Framework Changes A modification of the robottelo framework No-CherryPick PR doesnt need CherryPick to previous branches PRT-Failed Indicates that latest PRT run is failed for the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants