Skip to content

Fixes #39213 - Import only OS-detection facts at registration time#11701

Merged
jeremylenz merged 1 commit into
Katello:masterfrom
pablomh:registration/surgical-fact-import
Jun 26, 2026
Merged

Fixes #39213 - Import only OS-detection facts at registration time#11701
jeremylenz merged 1 commit into
Katello:masterfrom
pablomh:registration/surgical-fact-import

Conversation

@pablomh

@pablomh pablomh commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Problem

During POST /rhsm/consumers, subscription-manager sends ~193 RHSM facts. Since 2018, all 193 are imported synchronously into Foreman's fact_values table, then imported again at step 6 (PUT /rhsm/consumers/:id, ~33 seconds later) — a redundant double import inherited from the original Dynflow registration flow that was removed in the same 2018 refactor.

The original reason for the step-2 import: step 6 was also async (Dynflow) in 2018, so it was unreliable for immediate status calculation. Step 6 is now synchronous and reliable.

Analysis

Only 3 facts are strictly needed at step 2 for RhelLifecycleStatus to be immediately correct:

  • distribution.name → sets host.operatingsystem.name
  • distribution.version → sets host.operatingsystem.major
  • uname.machine → sets host.architecture.name

HostFactImporter.parse_facts operates on the in-memory facts hash (not fact_values), so these 3 facts are sufficient to persist the correct OS and architecture on the host before finalize_registration runs and calls refresh_statuses([RhelLifecycleStatus]).

Everything else between step 2 and step 6 — Candlepin compliance checks (~13 calls), content access validation, Insights — operates on data from the POST body or its own sources. None read Foreman's fact_values.

Solution

Import only the 3 critical facts synchronously before finalize_registration, using additive: true (introduced in theforeman/foreman#10942) so existing fact_values are not deleted on re-registrations. The full 193-fact sync continues to happen at step 6 unchanged.

Test plan

  • New host registration: RhelLifecycleStatus correct immediately after POST /rhsm/consumers
  • Re-registration: existing facts preserved after step-2 subset import
  • additive: keyword threaded through SubscriptionFacet.update_facts

Fixes #39213
Depends on theforeman/foreman#10942 (additive import mode)

Summary by Sourcery

Limit synchronous fact import during host registration to only the OS and architecture detection facts while supporting additive fact updates.

Enhancements:

  • Introduce a constant list of critical RHSM facts required at registration time for immediate RHEL lifecycle status calculation.
  • Update registration flow to import only these critical facts synchronously and defer full fact import to the later consumer update step.
  • Extend SubscriptionFacet.update_facts to support an additive import mode and pass it through to HostFactImporter so existing facts are preserved.

Summary by CodeRabbit

  • Bug Fixes
    • Registration now saves critical system details (distribution name, version, machine type) immediately after creation.
    • Critical facts are imported in an additive mode so previously collected host data is preserved.
    • Failures when saving these facts are logged and no longer block or abort registration.
    • Full/further facts import is deferred to a later finalize step rather than during initial creation.

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

SubscriptionFacet.update_facts gains an additive: false keyword argument forwarded to ::HostFactImporter#import_facts. RegistrationManager adds REGISTRATION_CRITICAL_FACTS and, after Candlepin consumer creation, imports only that critical subset via SubscriptionFacet.update_facts(host, critical, additive: true) (errors are rescued and logged).

Changes

Cohort / File(s) Summary
Subscription Facet Enhancement
app/models/katello/host/subscription_facet.rb
Method signature changed to update_facts(host, rhsm_facts, additive: false) and now forwards additive to ::HostFactImporter#import_facts. Early-return and fact mutations unchanged.
Registration Manager Update
app/services/katello/registration_manager.rb
Added REGISTRATION_CRITICAL_FACTS (distribution.name, distribution.version, uname.machine). After creating the Candlepin consumer, slices consumer_params[:facts] to the critical subset and calls SubscriptionFacet.update_facts(host, critical, additive: true) inside a rescue that logs warnings; full facts import is deferred/removed from candlepin creation path.

Sequence Diagram

sequenceDiagram
    participant Client
    participant RegistrationManager
    participant Candlepin
    participant SubscriptionFacet
    participant HostFactImporter

    Client->>RegistrationManager: register_host(host, consumer_params)
    RegistrationManager->>Candlepin: create consumer
    Candlepin-->>RegistrationManager: consumer created

    rect rgba(100,150,200,0.5)
    Note over RegistrationManager: extract critical facts
    RegistrationManager->>RegistrationManager: critical = consumer_params[:facts].slice(REGISTRATION_CRITICAL_FACTS)
    RegistrationManager->>SubscriptionFacet: update_facts(host, critical, additive: true)
    SubscriptionFacet->>HostFactImporter: import_facts(rhsm_facts, additive: true)
    HostFactImporter-->>SubscriptionFacet: import complete
    end

    RegistrationManager->>RegistrationManager: finalize_registration (full facts import later)
    RegistrationManager-->>Client: registration complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: importing only OS-detection facts at registration time instead of full facts, directly addressing issue #39213.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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 reviewed your changes and they look great!


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/services/katello/registration_manager.rb (1)

247-258: ⚠️ Potential issue | 🟠 Major

Clarify: Should this full fact import remain?

Per the PR objectives, "Import only the three OS-detection facts synchronously at registration (step 2)." This line imports all facts during registration, which contradicts the stated optimization goal. If the intent is to defer the full fact sync to step 6 (PUT /rhsm/consumers/:id), this call should likely be removed.

If this import is intentionally retained for Candlepin synchronization purposes, please clarify in the code comments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/katello/registration_manager.rb` around lines 247 - 258, The
create_in_candlepin method currently calls
::Katello::Host::SubscriptionFacet.update_facts(host, consumer_params[:facts])
which imports all facts at registration and contradicts the PR objective to
import only the three OS-detection facts synchronously; either remove this
full-fact import or change it to extract and pass only the three OS-detection
facts (e.g., kernel, osrelease, operatingsystem or whatever the project’s
canonical three are) before calling update_facts, and if you intentionally must
keep the full import for Candlepin sync add a clear code comment above the
::Katello::Host::SubscriptionFacet.update_facts call explaining that Candlepin
requires full fact sync at registration and why it cannot be deferred to the
later PUT step.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/services/katello/registration_manager.rb`:
- Around line 203-209: Remove the full facts import in create_in_candlepin so
only the critical facts are imported during registration: locate the
create_in_candlepin method and remove or disable the call to
::Katello::Host::SubscriptionFacet.update_facts that applies the entire
consumer_params[:facts] payload, leaving the earlier logic that slices
consumer_params[:facts] with REGISTRATION_CRITICAL_FACTS and only updates those
critical facts (additive: true); ensure no other code in create_in_candlepin
re-applies the full facts set so the full fact set will arrive later via PUT
/rhsm/consumers/:id as intended.

---

Outside diff comments:
In `@app/services/katello/registration_manager.rb`:
- Around line 247-258: The create_in_candlepin method currently calls
::Katello::Host::SubscriptionFacet.update_facts(host, consumer_params[:facts])
which imports all facts at registration and contradicts the PR objective to
import only the three OS-detection facts synchronously; either remove this
full-fact import or change it to extract and pass only the three OS-detection
facts (e.g., kernel, osrelease, operatingsystem or whatever the project’s
canonical three are) before calling update_facts, and if you intentionally must
keep the full import for Candlepin sync add a clear code comment above the
::Katello::Host::SubscriptionFacet.update_facts call explaining that Candlepin
requires full fact sync at registration and why it cannot be deferred to the
later PUT step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 762da2c0-c244-4b48-8782-c9dd9f668412

📥 Commits

Reviewing files that changed from the base of the PR and between cc28088 and 0736af9f82284a5212e2bfed9b397fadbd04cc8f.

📒 Files selected for processing (2)
  • app/models/katello/host/subscription_facet.rb
  • app/services/katello/registration_manager.rb

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 0736af9 to 25524db Compare April 2, 2026 19:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/services/katello/registration_manager.rb`:
- Around line 203-209: The fact-import block for REGISTRATION_CRITICAL_FACTS
(using consumer_params[:facts] and
::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true))
was moved outside the create_in_candlepin rescue/rollback path so any exception
there bypasses the rollback; move this block back inside the existing
create_in_candlepin rescue/ensure cleanup path (or wrap it in the same
begin/rescue that calls the rollback) so failures during critical-fact import
trigger the same rollback/cleanup logic as create_in_candlepin and do not leave
a partially registered host.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 030790c9-6809-4db5-a83c-2bee92365259

📥 Commits

Reviewing files that changed from the base of the PR and between 0736af9f82284a5212e2bfed9b397fadbd04cc8f and 25524dbea0ac7b77407f1792589bb8203dcd8e6d.

📒 Files selected for processing (2)
  • app/models/katello/host/subscription_facet.rb
  • app/services/katello/registration_manager.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/katello/host/subscription_facet.rb

Comment on lines +203 to +209
# Import only the facts needed for OS/architecture detection so that
# RhelLifecycleStatus is correct immediately after registration.
# The full fact set arrives via PUT /rhsm/consumers/:id (step 6).
unless consumer_params[:facts].blank?
critical = consumer_params[:facts].slice(*REGISTRATION_CRITICAL_FACTS)
::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true) if critical.present?
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the new fact import inside the existing cleanup path.

This block now sits outside the create_in_candlepin rescue, so an exception during fact import skips the rollback logic and can leave a partially registered host behind.

🩹 Proposed fix
         User.as_anonymous_admin do
           begin
             create_in_candlepin(host, content_view_environments, consumer_params, activation_keys)
+            unless consumer_params[:facts].blank?
+              critical = consumer_params[:facts].slice(*REGISTRATION_CRITICAL_FACTS)
+              ::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true) if critical.present?
+            end
           rescue StandardError => e
             # we can't call CP here since something bad already happened. Just clean up our DB as best as we can.
             host.subscription_facet.try(:destroy!)
             new_host ? remove_partially_registered_new_host(host) : remove_host_artifacts(host)
             raise e
           end
-
-          # Import only the facts needed for OS/architecture detection so that
-          # RhelLifecycleStatus is correct immediately after registration.
-          # The full fact set arrives via PUT /rhsm/consumers/:id (step 6).
-          unless consumer_params[:facts].blank?
-            critical = consumer_params[:facts].slice(*REGISTRATION_CRITICAL_FACTS)
-            ::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true) if critical.present?
-          end
 
           finalize_registration(host)
         end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Import only the facts needed for OS/architecture detection so that
# RhelLifecycleStatus is correct immediately after registration.
# The full fact set arrives via PUT /rhsm/consumers/:id (step 6).
unless consumer_params[:facts].blank?
critical = consumer_params[:facts].slice(*REGISTRATION_CRITICAL_FACTS)
::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true) if critical.present?
end
User.as_anonymous_admin do
begin
create_in_candlepin(host, content_view_environments, consumer_params, activation_keys)
unless consumer_params[:facts].blank?
critical = consumer_params[:facts].slice(*REGISTRATION_CRITICAL_FACTS)
::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true) if critical.present?
end
rescue StandardError => e
# we can't call CP here since something bad already happened. Just clean up our DB as best as we can.
host.subscription_facet.try(:destroy!)
new_host ? remove_partially_registered_new_host(host) : remove_host_artifacts(host)
raise e
end
finalize_registration(host)
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/katello/registration_manager.rb` around lines 203 - 209, The
fact-import block for REGISTRATION_CRITICAL_FACTS (using consumer_params[:facts]
and ::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive:
true)) was moved outside the create_in_candlepin rescue/rollback path so any
exception there bypasses the rollback; move this block back inside the existing
create_in_candlepin rescue/ensure cleanup path (or wrap it in the same
begin/rescue that calls the rollback) so failures during critical-fact import
trigger the same rollback/cleanup logic as create_in_candlepin and do not leave
a partially registered host.

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 25524db to 38878a1 Compare April 2, 2026 20:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/services/katello/registration_manager.rb`:
- Around line 208-213: The re-registration flow currently deletes
host.rhsm_fact_values in remove_host_artifacts (invoked via unregister_host with
unregistering: true) before ::Katello::Host::SubscriptionFacet.update_facts is
called, so using additive: true cannot restore prior RHSM facts; modify the
unregister/remove path to accept and propagate a flag (e.g., clear_rhsm_facts:
false) when performing re-registration cleanup, update remove_host_artifacts to
skip deleting host.rhsm_fact_values when that flag is false, and ensure the
re-registration caller passes clear_rhsm_facts: false so the old RHSM facts
remain available for ::Katello::Host::SubscriptionFacet.update_facts(additive:
true).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5fd6a583-d0bf-4c20-9e89-103dd0ec61e6

📥 Commits

Reviewing files that changed from the base of the PR and between 25524dbea0ac7b77407f1792589bb8203dcd8e6d and 38878a14f5d25a13d5453fa374c4ec042cbe1ad0.

📒 Files selected for processing (2)
  • app/models/katello/host/subscription_facet.rb
  • app/services/katello/registration_manager.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/katello/host/subscription_facet.rb

Comment on lines +208 to +213
unless consumer_params[:facts].blank?
critical = consumer_params[:facts].slice(*REGISTRATION_CRITICAL_FACTS)
if critical.present?
begin
::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true)
rescue StandardError => e

@coderabbitai coderabbitai Bot Apr 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

additive: true cannot preserve the previous RHSM facts after re-registration.

For existing hosts, Line 169 calls unregister_host(..., unregistering: true), and that path reaches remove_host_artifacts, which deletes host.rhsm_fact_values at Line 367 before this block runs. By the time Line 212 executes, the old RHSM fact set is already gone, so the additive import only preserves facts written earlier in this request—not the prior registration facts this change is trying to keep until step 6. Consider threading a clear_rhsm_facts: false-style flag through the re-registration cleanup path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/katello/registration_manager.rb` around lines 208 - 213, The
re-registration flow currently deletes host.rhsm_fact_values in
remove_host_artifacts (invoked via unregister_host with unregistering: true)
before ::Katello::Host::SubscriptionFacet.update_facts is called, so using
additive: true cannot restore prior RHSM facts; modify the unregister/remove
path to accept and propagate a flag (e.g., clear_rhsm_facts: false) when
performing re-registration cleanup, update remove_host_artifacts to skip
deleting host.rhsm_fact_values when that flag is false, and ensure the
re-registration caller passes clear_rhsm_facts: false so the old RHSM facts
remain available for ::Katello::Host::SubscriptionFacet.update_facts(additive:
true).

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.

The additive: true is intentional and necessary for a specific edge case that the analysis missed.

remove_host_artifacts (which deletes rhsm_fact_values at line 367) is only reached if candlepin_consumer_destroy at line 142 succeeds. When the host is already gone from Candlepin, candlepin_consumer_destroy raises RestClient::Gone, which is rescued at line 170-172 — and remove_host_artifacts is never called. In that case host.rhsm_fact_values still contains the prior fact set.

So the two paths are:

  • Normal re-registration: remove_host_artifacts runs → facts cleared → additive: true is a no-op (but harmless)
  • RestClient::Gone (host already deleted from Candlepin): remove_host_artifacts never runs → prior facts still exist → additive: true preserves them while importing only the critical subset

additive: true is the right choice precisely because of the second path.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@pablomh, thank you for the detailed walkthrough — you're right, and my original comment was incorrect.

The key I missed is the rescue RestClient::Gone at lines 170–172 of register_host. If RestClient::Gone propagates out of unregister_host (most likely from host.subscription_facet.try(:destroy!) triggering a Candlepin callback, since candlepin_consumer_destroy itself silently swallows that exception), execution jumps past remove_host_artifacts entirely. In that path rhsm_fact_values are still intact from the prior registration, and additive: true correctly preserves them while only importing the critical subset.

The two paths are indeed:

Path remove_host_artifacts runs? Effect of additive: true
Normal re-registration ✅ Yes — facts cleared No-op (harmless)
RestClient::Gone (host already gone from CP) ❌ No — facts intact Preserves prior facts

additive: true is the right choice for exactly this reason. I'll withdraw my original comment.


✏️ Learnings added
Learnt from: pablomh
Repo: Katello/katello PR: 11701
File: app/services/katello/registration_manager.rb:208-213
Timestamp: 2026-04-02T20:20:03.389Z
Learning: In `app/services/katello/registration_manager.rb`, the `additive: true` flag passed to `SubscriptionFacet.update_facts` during critical-facts import at registration is intentional. When `RestClient::Gone` is raised during `unregister_host` (e.g., from `subscription_facet.destroy!` Candlepin callbacks), it is rescued at the outer `rescue RestClient::Gone` block inside `register_host`, causing `remove_host_artifacts` to be skipped. In that code path `host.rhsm_fact_values` still hold the prior registration's facts, and `additive: true` correctly preserves them while importing only the critical OS-detection subset. In the normal re-registration path `remove_host_artifacts` runs first and clears the facts, making `additive: true` a harmless no-op.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

if critical.present?
begin
::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true)
rescue StandardError => e

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is this rescue guarding against? Does the FactImporter raise any of its own errors? It's reasonable to believe that fact import (which is ultimately just creating or updating some records in the DB) should succeed.

If the rescue is reached there's no signal that something has gone wrong - it's ultimately swallowed - and could be a long time before a regression is found & reported. By letting the error raise the underlying bug can be caught and addressed - hopefully by unit or integration tests.

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.

Good catch — rescue StandardError was too broad. Narrowed it to rescue Foreman::Exception with an explanation in the comment.

HostFactImporter#import_facts explicitly raises Foreman::Exception when host.build? is true (raise ::Foreman::Exception.new('Host is pending for Build') if host.build?). This is the one specific expected failure: during the provisioning workflow, subscription-manager registers the host while build=true (the flag hasn't been cleared yet), so Foreman intentionally refuses to import facts. Registration has already succeeded at that point and step 6 will import the full fact set.

Everything else — DB failures, connection errors, bugs in the importer — now propagates.

@pablomh pablomh Apr 2, 2026

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 extracted the entire block into import_critical_registration_facts so the rescue scope and intent are explicit rather than buried in register_host.

rescue StandardError is intentional and the comment in the new method explains why: this import is best-effort. By the time it runs, Candlepin registration has already succeeded — the host IS registered. Propagating any failure here would mislead the client into thinking registration failed when it actually did not. Step 6 (PUT /rhsm/consumers/:id) will import the full fact set regardless.

The method name makes the non-fatal contract clear, so the broad rescue is no longer hidden in the middle of a long method.

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 38878a1 to 559ff78 Compare April 2, 2026 21:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
app/services/katello/registration_manager.rb (1)

211-219: ⚠️ Potential issue | 🟠 Major

This rescue does not protect the build-host path it documents.

SubscriptionFacet.update_facts already returns when host.build?, so the Foreman::Exception rescue here will not fire for the provisioning flow described in Lines 213-216. That leaves this block masking only unexpected importer failures, while any other exception still escapes after Candlepin creation and bypasses the cleanup in Lines 194-200. Please either let update_facts fail fast here or move the whole critical-import step under the existing rollback path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/katello/registration_manager.rb` around lines 211 - 219, The
rescue around ::Katello::Host::SubscriptionFacet.update_facts masks unexpected
failures and doesn't protect the documented build-host path because update_facts
already returns when host.build?; remove the rescue so exceptions from
::Katello::Host::SubscriptionFacet.update_facts bubble and trigger the existing
rollback/cleanup, or alternatively explicitly guard the call with if host.build?
skip logic then call update_facts without rescuing—modify the block around
::Katello::Host::SubscriptionFacet.update_facts (and any HostFactImporter
interactions) so that only an explicit host.build? check suppresses the call,
but all other exceptions are allowed to propagate to the existing rollback path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/services/katello/registration_manager.rb`:
- Around line 211-219: The rescue around
::Katello::Host::SubscriptionFacet.update_facts masks unexpected failures and
doesn't protect the documented build-host path because update_facts already
returns when host.build?; remove the rescue so exceptions from
::Katello::Host::SubscriptionFacet.update_facts bubble and trigger the existing
rollback/cleanup, or alternatively explicitly guard the call with if host.build?
skip logic then call update_facts without rescuing—modify the block around
::Katello::Host::SubscriptionFacet.update_facts (and any HostFactImporter
interactions) so that only an explicit host.build? check suppresses the call,
but all other exceptions are allowed to propagate to the existing rollback path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10014a41-3ef3-4611-9eff-f9f56d98c316

📥 Commits

Reviewing files that changed from the base of the PR and between 38878a14f5d25a13d5453fa374c4ec042cbe1ad0 and 559ff788fbadf4d459cdf55c704d3b2e8351fc77.

📒 Files selected for processing (2)
  • app/models/katello/host/subscription_facet.rb
  • app/services/katello/registration_manager.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/katello/host/subscription_facet.rb

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 559ff78 to 716b178 Compare April 2, 2026 22:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/services/katello/registration_manager.rb (1)

330-341: Keep the expected build-host skip out of warn.

Line 335 currently warns for every importer failure, but provisioning registrations can legitimately hit the build-mode guard. A small host.build? fast-path here would keep logs quieter and make the remaining warnings much more actionable.

💡 One way to keep the expected path quiet
       def import_critical_registration_facts(host, facts)
         return if facts.blank?
+        if host.build?
+          Rails.logger.debug("Skipping critical fact import for build host #{host&.name}")
+          return
+        end
         critical = facts.slice(*REGISTRATION_CRITICAL_FACTS)
         return if critical.blank?
         ::Katello::Host::SubscriptionFacet.update_facts(host, critical, additive: true)
       rescue StandardError => e
@@
-        Rails.logger.warn("Failed to import critical facts for host #{host&.name}: #{e.message}")
+        Rails.logger.warn("Failed to import critical facts for host #{host&.name}: #{e.class}: #{e.message}")
       end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/katello/registration_manager.rb` around lines 330 - 341, The
rescue in import_critical_registration_facts logs any failure, but expected
provisioning build-host skips should not produce warnings; add a fast-path that
detects build-mode and suppresses the warning. Modify
import_critical_registration_facts (method in registration_manager.rb) to return
early if host&.build? (or, in the rescue, skip logging when host&.build? is
true) before calling ::Katello::Host::SubscriptionFacet.update_facts or emitting
the Rails.logger.warn, so legitimate build-mode bypasses do not create warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/services/katello/registration_manager.rb`:
- Around line 330-341: The rescue in import_critical_registration_facts logs any
failure, but expected provisioning build-host skips should not produce warnings;
add a fast-path that detects build-mode and suppresses the warning. Modify
import_critical_registration_facts (method in registration_manager.rb) to return
early if host&.build? (or, in the rescue, skip logging when host&.build? is
true) before calling ::Katello::Host::SubscriptionFacet.update_facts or emitting
the Rails.logger.warn, so legitimate build-mode bypasses do not create warnings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62631df4-bd83-4ecb-8c77-a7a92038184d

📥 Commits

Reviewing files that changed from the base of the PR and between 559ff788fbadf4d459cdf55c704d3b2e8351fc77 and 716b1784635888c2f94d51d1c20f477fd252524e.

📒 Files selected for processing (2)
  • app/models/katello/host/subscription_facet.rb
  • app/services/katello/registration_manager.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/katello/host/subscription_facet.rb

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 716b178 to c620d7c Compare April 2, 2026 22:46
@pablomh

pablomh commented Apr 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the two improvements from the latest review:

  • Added e.class to the warning log and removed the unnecessary &. safe-navigator on host.name (which is never nil at this point)
  • Simplified the double early-return into facts&.slice(*REGISTRATION_CRITICAL_FACTS) + single blank? check
  • Added three unit tests covering the happy path, the no-critical-facts skip, and the rescue path

On the host.build? guard suggestion: not adding it. SubscriptionFacet.update_facts already has return if host.build? at line 180 of subscription_facet.rb — it returns silently before reaching HostFactImporter. Adding a duplicate guard in import_critical_registration_facts would be defensive of an implementation detail in a callee, not a contract boundary we own.

@pablomh

pablomh commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

Performance testing results

Tested on a medium Satellite (8 CPU, 32 GB RAM) with 3 registration topologies (direct, standalone capsule, load-balanced capsule pair) across concurrency levels from 56 to 560 concurrent hosts. This PR was tested together with its dependency theforeman/foreman#10942 (bulk-insert facts + additive import mode).

Direct impact of this PR (193 → 3 facts at step 2):

By deferring 190 facts from the synchronous POST /rhsm/consumers path to the later PUT /rhsm/consumers/:id call, this PR removes the vast majority of fact-import work from the registration critical path. Even with theforeman/foreman#10942's bulk INSERT optimisation, importing 193 facts in a single statement still involves parsing, validation, and writing 193 rows — this PR reduces that to 3 rows at the time-sensitive step.

The deferred facts arrive at step 6 (PUT /rhsm/consumers/:id) which runs ~33 seconds later when Puma thread contention from the initial registration burst has subsided. This effectively moves the bulk of fact-import DB load out of the peak contention window.

Combined impact with theforeman/foreman#10942 (measured in the same batch):

Metric Before After Change
POST /rhsm/consumers P50 15,288 ms 8,247 ms -46%
POST /rhsm/consumers P95 27,541 ms 18,195 ms -34%
GET /register (script delivery) P50 770 ms 298 ms -61%
POST /register (host record) P50 1,648 ms 825 ms -50%
ActiveRecord time P50 3,413 ms 566 ms -83%
First-try success rate (56-560 concurrent) 68% 90% +22 pp

The P50 improvement in GET /register and POST /register (which are not directly related to fact import) reflects reduced Puma queue depth — with each POST /rhsm/consumers completing faster, subsequent requests in the queue start sooner, improving latency across all endpoints.

Note: The 90% success rate is limited by the subscription-manager client timeout (180s). At 504+ concurrent, queue depth causes some registrations to exceed this wall-clock budget despite the server completing every request successfully (zero server-side errors). The PRs themselves are not the limiting factor at that scale — infrastructure timeouts and Puma thread count are.

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from c620d7c to a4632de Compare May 9, 2026 15:56
@pablomh

pablomh commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

CI fails with Metrics/ClassLength on registration_manager_test.rb — the class is at 330 lines (limit 297) after rebasing on top of the recently merged test_registration_dead_candlepin_clears_cache test from #11696.

Would you prefer we split the test class (e.g. a separate RegistrationManagerFactImportTest context) or disable the cop inline for this file?

@jturel

jturel commented May 9, 2026

Copy link
Copy Markdown
Member

Pausing my review here until #11692 reaches a resolution.

@jturel

jturel commented May 26, 2026

Copy link
Copy Markdown
Member

@pablomh can you rebase this? Then we'll get a proper review going

@jturel

jturel commented May 26, 2026

Copy link
Copy Markdown
Member

CI fails with Metrics/ClassLength on registration_manager_test.rb — the class is at 330 lines (limit 297) after rebasing on top of the recently merged test_registration_dead_candlepin_clears_cache test from #11696.

Would you prefer we split the test class (e.g. a separate RegistrationManagerFactImportTest context) or disable the cop inline for this file?

If this is still applicable (it might not be since I removed some tests from that file recently) then I'm good with disabling that cop for the class you mentioned.

@pablomh pablomh force-pushed the registration/surgical-fact-import branch from a4632de to 8a9361d Compare May 26, 2026 22:55
@jturel

jturel commented May 27, 2026

Copy link
Copy Markdown
Member

The test failures do look related to this PR:


Error: Failure: Katello::SubscriptionFacetTest#test_update_foreman_facts

ArgumentError: wrong number of arguments (given 1, expected 2)
    katello/app/models/katello/host/subscription_facet.rb:179:in `update_facts'
    katello/test/models/host/subscription_facet_test.rb:167:in `test_update_foreman_facts'

Error: Failure: Katello::SubscriptionFacetTest#test_update_facts_with_centos_no_minor_version

ArgumentError: wrong number of arguments (given 1, expected 2)
    katello/app/models/katello/host/subscription_facet.rb:179:in `update_facts'
    katello/test/models/host/subscription_facet_test.rb:178:in `test_update_facts_with_centos_no_minor_version'

Error: Failure: Katello::SubscriptionFacetTest#test_update_foreman_facts_without_distribution_version

ArgumentError: wrong number of arguments (given 1, expected 2)
    katello/app/models/katello/host/subscription_facet.rb:179:in `update_facts'
    katello/test/models/host/subscription_facet_test.rb:223:in `test_update_foreman_facts_without_distribution_version'

Error: Failure: Katello::SubscriptionFacetTest#test_update_foreman_facts_with_os_version

ArgumentError: wrong number of arguments (given 1, expected 2)
    katello/app/models/katello/host/subscription_facet.rb:179:in `update_facts'
    katello/test/models/host/subscription_facet_test.rb:211:in `test_update_foreman_facts_with_os_version'

Error: Failure: Katello::SubscriptionFacetTest#test_update_foreman_facts_with_no_centos_different_major_and_no_minor_version

ArgumentError: wrong number of arguments (given 1, expected 2)
    katello/app/models/katello/host/subscription_facet.rb:179:in `update_facts'
    katello/test/models/host/subscription_facet_test.rb:191:in `test_update_foreman_facts_with_no_centos_different_major_and_no_minor_version'

@pablomh

pablomh commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

The remaining failures are tests created based on the addition of the additive: keyword in theforeman/foreman#10980. Until it's merged, the won't pass.

@jturel

jturel commented May 27, 2026

Copy link
Copy Markdown
Member

I forgot about that. Ping me when it's green :)

@pablomh

pablomh commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

I'll put it DRAFT mode until it's merged.

@pablomh pablomh marked this pull request as draft May 27, 2026 18:16
@jeremylenz

Copy link
Copy Markdown
Member

Please rebase and fix conflicts here, thx :)

@pablomh

pablomh commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@jturel, friendly ping now that all the reqs are met and the CI is green :)

@jeremylenz jeremylenz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've already tested this with theforeman/foreman#10980 and am happy with the result. Just leaving one nit below

Comment thread app/services/katello/registration_manager.rb Outdated
@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 899a87a to 952ad38 Compare June 25, 2026 22:00
Import only the registration-time facts needed for immediate OS and architecture detection while preserving additive fact-import behavior. Keep the stricter registration failure handling, remove the unnecessary Candlepin UUID fallback, and align the registration tests with the resource layer's indifferent-access return contract.
@pablomh pablomh force-pushed the registration/surgical-fact-import branch from 952ad38 to b4f022d Compare June 25, 2026 22:00

@jeremylenz jeremylenz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @pablomh!

ACK 👍

@jeremylenz jeremylenz merged commit 358d4dd into Katello:master Jun 26, 2026
31 checks passed
@pablomh pablomh deleted the registration/surgical-fact-import branch June 26, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants