-
Notifications
You must be signed in to change notification settings - Fork 50
Fix IoP host UUID data consistency issues #1131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix IoP host UUID data consistency issues #1131
Conversation
Reviewer's GuideEnsures hosts in IoP mode consistently use the subscription manager UUID for Insights operations by introducing a unified insights_uuid accessor, a synchronization helper to fix stale UUIDs, updating all call sites to use the new accessor, and adding factories and tests to cover IoP/non-IoP and mismatch scenarios. Sequence diagram for generate_host_report with IoP UUID synchronizationsequenceDiagram
participant System
participant PackageProfileUploadExtensions as PackageProfileUploadExtensions
participant Host
participant ForemanRhCloud
participant SubscriptionFacet
participant InsightsFacet
System->>PackageProfileUploadExtensions: generate_host_report()
PackageProfileUploadExtensions->>Host: ensure_iop_insights_uuid()
alt IoP_mode
Host->>ForemanRhCloud: with_iop_smart_proxy?()
ForemanRhCloud-->>Host: true
Host->>Host: check insights_facet.present? and subscription_facet.present?
Host->>SubscriptionFacet: uuid
SubscriptionFacet-->>Host: sub_uuid
Host->>InsightsFacet: uuid
InsightsFacet-->>Host: insights_uuid
alt uuids_differ
Host->>InsightsFacet: update!(uuid: sub_uuid)
InsightsFacet-->>Host: updated
else uuids_match
Host-->>Host: no_update
end
else non_IoP_mode
Host-->>Host: ensure_iop_insights_uuid returns
end
PackageProfileUploadExtensions->>Host: insights()
Host-->>PackageProfileUploadExtensions: InsightsFacet or nil
PackageProfileUploadExtensions-->>System: continue upload flow
Class diagram for updated RhCloudHost UUID handlingclassDiagram
class Host {
+SubscriptionFacet subscription_facet
+InsightsFacet insights_facet
+InsightsFacet insights
+insights_facet()
+insights_uuid()
+ensure_iop_insights_uuid()
}
class RhCloudHost {
<<module>>
+insights_facet()
+insights_uuid()
+ensure_iop_insights_uuid()
}
class SubscriptionFacet {
+String uuid
}
class InsightsFacet {
+String uuid
+update!(uuid)
}
class ForemanRhCloud {
+with_iop_smart_proxy?()
}
Host ..|> RhCloudHost : includes
Host --> SubscriptionFacet : has_one
Host --> InsightsFacet : has_one
ForemanRhCloud ..> Host : configures_iop_mode
RhCloudHost ..> SubscriptionFacet : uses
RhCloudHost ..> InsightsFacet : uses
RhCloudHost ..> ForemanRhCloud : queries_mode
Flow diagram for insights_uuid resolution in IoP and non-IoP modesflowchart TD
A[call insights_uuid] --> B{ForemanRhCloud.with_iop_smart_proxy?}
B -- true (IoP) --> C{subscription_facet present?}
C -- yes --> D[return subscription_facet.uuid]
C -- no --> E[return nil]
B -- false (non-IoP) --> F{insights_facet present?}
F -- yes --> G[return insights_facet.uuid]
F -- no --> H[return nil]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- The
ensure_iop_insights_uuidhelper is currently invoked unconditionally ingenerate_host_report; consider guarding this call withForemanRhCloud.with_iop_smart_proxy?(or documenting why it is safe in non-IoP) so the synchronization logic is only run in the mode it’s intended for. - The new
:with_mismatched_insights_uuidand:with_synced_insights_uuidfactory traits rely on:with_subscriptionbut only document this in comments; consider asserting the presence ofsubscription_facet(e.g., raising if nil) so accidental misuse of these traits fails fast in tests.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `ensure_iop_insights_uuid` helper is currently invoked unconditionally in `generate_host_report`; consider guarding this call with `ForemanRhCloud.with_iop_smart_proxy?` (or documenting why it is safe in non-IoP) so the synchronization logic is only run in the mode it’s intended for.
- The new `:with_mismatched_insights_uuid` and `:with_synced_insights_uuid` factory traits rely on `:with_subscription` but only document this in comments; consider asserting the presence of `subscription_facet` (e.g., raising if nil) so accidental misuse of these traits fails fast in tests.
## Individual Comments
### Comment 1
<location> `lib/insights_cloud/async/connector_playbook_execution_reporter_task.rb:128-132` </location>
<code_context>
hosts_state = Hash[job_invocation.targeting.hosts.map do |host|
next unless host.insights&.uuid
[
- host.insights.uuid,
- task_status(job_invocation.sub_task_for_host(host), host.insights.uuid),
+ host.insights_uuid,
+ task_status(job_invocation.sub_task_for_host(host), host.insights_uuid),
]
end.compact]
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard still checks `host.insights&.uuid` but we now rely on `host.insights_uuid`, which may be present even when `host.insights` is nil.
In IoP, `host.insights_uuid` may come from the subscription facet even when `host.insights` is nil. With `next unless host.insights&.uuid`, those hosts are skipped and never added to `hosts_state`, despite having a valid `insights_uuid`. Consider guarding on `host.insights_uuid` instead so subscription-only hosts are included.
</issue_to_address>
### Comment 2
<location> `app/views/api/v2/hosts/insights/base.rabl:1-3` </location>
<code_context>
-attributes :uuid
-
+node :uuid do |facet|
+ facet&.host&.insights_uuid
+end
node :insights_hit_details do |facet|
</code_context>
<issue_to_address>
**suggestion (performance):** Switching UUID source to `facet.host.insights_uuid` may introduce extra queries or missing preloads.
This path now dereferences `facet.host.insights_uuid` instead of using the already-present `facet.uuid`, which can cause N+1s if `host` (and any associations `insights_uuid` relies on) aren’t preloaded for this view. Please confirm those preloads exist for this endpoint, or consider keeping `facet.uuid` for the non-IoP case and only resolving via `host` where strictly needed.
```suggestion
node :uuid do |facet|
facet&.uuid || facet&.host&.insights_uuid
end
```
</issue_to_address>
### Comment 3
<location> `test/controllers/insights_cloud/api/advisor_engine_controller_test.rb:15-19` </location>
<code_context>
end
test 'shows hosts with uuids' do
- uuids = [@host1.insights.uuid, @host2.insights.uuid]
+ uuids = [@host1.insights_uuid, @host2.insights_uuid]
get :host_details, params: { organization_id: @test_org.id, host_uuids: uuids }
assert_response :success
assert_template 'api/v2/advisor_engine/host_details'
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test for IoP mode to ensure host_details uses subscription_facet UUID when insights_facet is stale
Given the view now uses `host.insights_uuid`, IoP vs non-IoP behavior diverges when `insights_facet.uuid` is stale. Please add a test that:
- stubs `ForemanRhCloud.with_iop_smart_proxy?` to `true`,
- creates a host where `insights_facet.uuid` and `subscription_facet.uuid` differ, and
- verifies `host_details` returns the subscription UUID in `insights_uuid`.
This will confirm the data-consistency fix is covered at the API level.
Suggested implementation:
```ruby
end
test 'shows hosts with uuids' do
uuids = [@host1.insights_uuid, @host2.insights_uuid]
get :host_details, params: { organization_id: @test_org.id, host_uuids: uuids }
assert_response :success
assert_template 'api/v2/advisor_engine/host_details'
end
test 'in IoP mode host_details prefers subscription uuid when insights uuid is stale' do
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
stale_insights_uuid = 'stale-insights-uuid-123'
subscription_uuid = 'subscription-uuid-456'
host = FactoryBot.create(:host, organization: @test_org)
# Ensure the host has diverging facet UUIDs
if host.respond_to?(:build_insights_facet)
host.build_insights_facet(uuid: stale_insights_uuid)
else
host.insights_facet.uuid = stale_insights_uuid
end
if host.respond_to?(:build_subscription_facet)
host.build_subscription_facet(uuid: subscription_uuid)
else
host.subscription_facet.uuid = subscription_uuid
end
host.save!
host.reload
# Call host_details with the stale insights facet UUID
get :host_details, params: {
organization_id: @test_org.id,
host_uuids: [stale_insights_uuid]
}
assert_response :success
assert_template 'api/v2/advisor_engine/host_details'
body = JSON.parse(response.body)
# Assuming response contains a "hosts" array with "insights_uuid" per host
insights_uuids = body.fetch('hosts').map { |h| h['insights_uuid'] }
assert_includes insights_uuids, subscription_uuid
refute_includes insights_uuids, stale_insights_uuid
```
Depending on the existing test and response structure in `advisor_engine_controller_test.rb`, you may need to:
1. Adjust host creation:
- If you don't use `FactoryBot`, replace `FactoryBot.create(:host, organization: @test_org)` with the appropriate fixture or factory used elsewhere in this test file.
- If the facet associations are named differently (e.g. `:insights` instead of `:insights_facet`), update `build_insights_facet` / `build_subscription_facet` and the direct facet access accordingly.
2. Adjust JSON response parsing:
- If `host_details` renders a different JSON shape (for example `body['data']['hosts']` or `body['results']`), update `body.fetch('hosts')` to match the actual key path.
- If the field name is different (e.g. `'insights_id'` instead of `'insights_uuid'`), update the hash lookup in `map { |h| h['insights_uuid'] }`.
3. If the test suite uses a different stubbing library than `stubs` (e.g. `expects` or `stub` from another helper), align the call to how `ForemanRhCloud.with_iop_smart_proxy?` is stubbed in other tests.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
1f5c42e to
0d2766f
Compare
nofaralfasi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. tested and works as expected.
One potential bug to investigate: the guard condition in registration_manager_extensions.rb:16 may cause hosts with subscription_facet.uuid but no insights_facet.uuid to skip HBI unregistration in IoP mode.
app/controllers/concerns/foreman_rh_cloud/registration_manager_extensions.rb
Outdated
Show resolved
Hide resolved
|
Question about InsightsHit#host_uuid:
So the current code probably works fine in most cases. But for consistency with the pattern established in this PR (always reading from the authoritative source via |
I'm on the fence here. On one hand, I see what you're saying about consistency and source of truth. But on the other hand,
Let me know your thoughts. |
In IoP mode, hosts previously registered to Red Hat cloud could display stale insights facet UUIDs instead of their current subscription manager UUIDs. This caused data inconsistencies when viewing host details, recommendations, and during unregistration. Changes: - Add insights_uuid method that returns correct UUID based on mode - Add ensure_iop_insights_uuid to sync mismatched UUIDs - Update all call sites to use insights_uuid method - Add comprehensive test coverage (14 new tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add comment documenting ensure_iop_insights_uuid safety - Add subscription_facet assertions to factory traits - Fix guard in ConnectorPlaybookExecutionReporterTask to use insights_uuid - Add IoP mode test for advisor_engine_controller 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Preload the insights facet when fetching hosts in the API to prevent N+1 database queries when rendering host lists with facet data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
0d2766f to
630cc65
Compare
That makes sense - Since InsightsHit is hosted-only, and if these codepaths aren't touched in IoP, then adding the IoP branching logic there would violate the "minimal change to non-IoP code" principle and could introduce unnecessary risk. No changes needed here - thanks for the context! |
Fix IoP host UUID data consistency issues
User Impact
In IoP (Insights on Premise) mode, hosts that were previously registered to Red Hat Hybrid Cloud Console could display incorrect/stale UUIDs throughout the application. This caused:
This fix ensures IoP hosts always use their subscription manager UUID (the correct identifier for IoP environments) instead of stale cloud-assigned UUIDs. Hosts are automatically
synchronized when they upload package profiles.
Note: This does not remove the UUID field from insights facets, or allow nils there. The insights UUID is still kept in sync just as before, as a "just in case" kind of thing. But I've tried to move the actual decision points to look at subscription facet. This gets us closer to a single source of truth.
Technical Changes
insights_uuidmethod that returns the appropriate UUID based on operating mode:ensure_iop_insights_uuidmethod to automatically sync mismatched UUIDsinsights_uuidmethodTesting steps
Note that they are the same.
5. In rails console, run
Now, the uuids are different. This simulates a stale uuid from a host previously registered to Hosted insights.
Before checking out the PR:
Note that recommendations still display in Insights > Recommendations, and show the affected host. BUT they do NOT show up on the host details > Recommendations tab.
You may notice a failed API request to "foo" on the browser Network tab
After checking out the PR:
Recommendations will display on BOTH pages
No failed API requests