foreman_rh_cloud 7.0.46 → 8.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_inventory_upload/uploads_settings_controller.rb +1 -0
  3. data/app/models/insights_missing_host.rb +5 -0
  4. data/db/migrate/20230515140211_add_missing_hosts_table.foreman_rh_cloud.rb +13 -0
  5. data/lib/foreman_inventory_upload/async/remove_insights_hosts_job.rb +68 -0
  6. data/lib/foreman_inventory_upload/generators/tags.rb +3 -5
  7. data/lib/foreman_inventory_upload.rb +6 -1
  8. data/lib/foreman_rh_cloud/engine.rb +2 -1
  9. data/lib/foreman_rh_cloud/settings.rb +1 -0
  10. data/lib/foreman_rh_cloud/version.rb +1 -1
  11. data/lib/inventory_sync/async/host_result.rb +5 -0
  12. data/lib/inventory_sync/async/inventory_hosts_sync.rb +30 -1
  13. data/lib/inventory_sync/async/inventory_scheduled_sync.rb +13 -2
  14. data/lib/tasks/hybrid_cloud.rake +6 -5
  15. data/package.json +1 -1
  16. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +11 -3
  17. data/test/factories/insights_factories.rb +8 -0
  18. data/test/factories/inventory_upload_factories.rb +15 -2
  19. data/test/jobs/inventory_hosts_sync_test.rb +32 -0
  20. data/test/jobs/inventory_scheduled_sync_test.rb +12 -0
  21. data/test/jobs/remove_insights_hosts_job_test.rb +90 -0
  22. data/test/test_plugin_helper.rb +17 -0
  23. data/test/unit/slice_generator_test.rb +15 -9
  24. data/test/unit/tags_generator_test.rb +22 -7
  25. data/webpack/ForemanInventoryUpload/Components/InventorySettings/AdvancedSetting/AdvancedSettingsConstants.js +7 -0
  26. data/webpack/ForemanInventoryUpload/Components/InventorySettings/InventorySettingsSelectors.js +3 -0
  27. data/webpack/ForemanInventoryUpload/Components/InventorySettings/__tests__/__snapshots__/InventorySettings.test.js.snap +4 -0
  28. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1383b077fcadc72b19517f0ed1173aec5ac958a207e8b04a6119e91b75b062c0
4
- data.tar.gz: 98783f069cfd8cbc89e2a295b0433437ec3d88b168ccef7c2b7c42b851d07929
3
+ metadata.gz: e4c84356ce25f9683795e8adb7ab42d223cef0a4ba84a4a8d9f5836cc6b80986
4
+ data.tar.gz: 5f86cd9c8240aff463e25620a5e0bb47fbc15ab7318647f40918d98a85513ab8
5
5
  SHA512:
6
- metadata.gz: 711977116c1b74dfd5b528bf7298df5f9c5da3dda6f63a788ed4ea8f82e76b62139d5d4417b4af19ca3e564e0764d99cc38ca30a496b1f78fd170829fef11017
7
- data.tar.gz: 78f1a3584c4eea9f64d49cf4be866cd41074e04264c860d22332205124c312c62372cde26435e1e4bfe7303a7574754cbab3f49e3f1c3a31a6abe65c4363890c
6
+ metadata.gz: 0a7765f56a4d7e695dd253b17fd87820e8455fbe95a23450e2577125f65f20acc813723a2ff036c81d6b41c4d75d1bcf3b3aea65fb1b4042b916a68a117340a5
7
+ data.tar.gz: d8e8db10a0fc49b9741d38f93e5d59917f85b7ac6fcc8e3007dcb84346ccd15c07bd644a91de1a5ea38942cbb5b27553d5a7c59315e317fe79d1b13b542151bd
@@ -6,6 +6,7 @@ module ForemanInventoryUpload
6
6
  hostObfuscationEnabled: Setting[:obfuscate_inventory_hostnames],
7
7
  ipsObfuscationEnabled: Setting[:obfuscate_inventory_ips],
8
8
  excludePackagesEnabled: Setting[:exclude_installed_packages],
9
+ allowAutoInsightsMismatchDelete: Setting[:allow_auto_insights_mismatch_delete],
9
10
  CloudConnectorStatus: ForemanInventoryUpload::UploadsSettingsController.cloud_connector_status,
10
11
  lastSyncTask: last_successful_inventory_sync_task,
11
12
  }, status: :ok
@@ -0,0 +1,5 @@
1
+ class InsightsMissingHost < ApplicationRecord
2
+ belongs_to :organization
3
+
4
+ scoped_search on: [:name, :insights_id, :rhsm_id, :ip_address]
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMissingHostsTable < ActiveRecord::Migration[6.1]
4
+ def change
5
+ create_table :insights_missing_hosts do |t|
6
+ t.integer :organization_id
7
+ t.string :name
8
+ t.string :insights_id
9
+ t.string :rhsm_id
10
+ t.string :ip_address
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,68 @@
1
+ module ForemanInventoryUpload
2
+ module Async
3
+ class RemoveInsightsHostsJob < ::Actions::EntryAction
4
+ include ForemanRhCloud::CertAuth
5
+
6
+ def plan(search_term, organization_id)
7
+ plan_self(search_term: search_term, organization_id: organization_id)
8
+ end
9
+
10
+ def run
11
+ logger.debug("Attempting to remove hosts by search term: #{search_term}")
12
+
13
+ host_uuids = InsightsMissingHost.search_for(search_term).pluck(:insights_id)
14
+
15
+ page_number = 1
16
+ while (current_page = host_uuids.paginate(page: page_number, per_page: page_size)).present?
17
+ logger.debug("Removing #{(page_number - 1) * page_size} - #{page_number * page_size}/#{current_page.total_entries} hosts: #{current_page.join(',')}")
18
+ response = delete_page(current_page, organization)
19
+ # write the response in case we want to track it later
20
+ output["response_page#{page_number}"] = response.body
21
+
22
+ # remove host records that reported success after deletion
23
+ if response.code >= 200 && response.code < 300
24
+ remove_host_records(current_page)
25
+ else
26
+ error! "Cloud responded with code: #{response.code}"
27
+ end
28
+
29
+ page_number += 1
30
+ end
31
+ end
32
+
33
+ def logger
34
+ Foreman::Logging.logger('background')
35
+ end
36
+
37
+ def search_term
38
+ input[:search_term]
39
+ end
40
+
41
+ def organization
42
+ @organization ||= Organization.find_by(id: input[:organization_id])
43
+ end
44
+
45
+ def delete_page(host_uuids, organization)
46
+ execute_cloud_request(
47
+ organization: organization,
48
+ method: :delete,
49
+ url: ForemanInventoryUpload.hosts_by_ids_url(host_uuids),
50
+ headers: {
51
+ content_type: :json,
52
+ }
53
+ )
54
+ rescue RestClient::ExceptionWithResponse => error_response
55
+ error_response.response
56
+ end
57
+
58
+ def remove_host_records(uuids)
59
+ InsightsMissingHost.where(insights_id: uuids).delete_all
60
+ end
61
+
62
+ def page_size
63
+ # the_most_conservative_url_size_limit(2083) / uri_size(36) with some spares for the domain name
64
+ 40
65
+ end
66
+ end
67
+ end
68
+ end
@@ -45,11 +45,9 @@ module ForemanInventoryUpload
45
45
  end
46
46
 
47
47
  def content_data
48
- [
49
- ['lifecycle_environment', @host.lifecycle_environment&.name],
50
- ['content_view', @host.content_view&.name],
51
- ] +
52
- (@host.activation_keys || []).map { |item| ['activation_key', item.name] }
48
+ (@host.lifecycle_environments.uniq || []).map { |item| ['lifecycle_environment', item.name] } +
49
+ (@host.activation_keys || []).map { |item| ['activation_key', item.name] } +
50
+ (@host.content_views || []).map { |item| ['content_view', item.name] }
53
51
  end
54
52
 
55
53
  def satellite_server_data
@@ -71,7 +71,7 @@ module ForemanInventoryUpload
71
71
  end
72
72
 
73
73
  def self.inventory_base_url
74
- ForemanRhCloud.cert_base_url + "/api/inventory/v1/hosts"
74
+ "#{ForemanRhCloud.cert_base_url}/api/inventory/v1/hosts"
75
75
  end
76
76
 
77
77
  def self.inventory_export_url
@@ -82,4 +82,9 @@ module ForemanInventoryUpload
82
82
  def self.inventory_self_url
83
83
  inventory_base_url + "?hostname_or_id=#{ForemanRhCloud.foreman_host.fqdn}"
84
84
  end
85
+
86
+ def self.hosts_by_ids_url(host_ids)
87
+ host_ids_string = host_ids.join(',')
88
+ "#{inventory_base_url}/#{host_ids_string}"
89
+ end
85
90
  end
@@ -127,7 +127,8 @@ module ForemanRhCloud
127
127
 
128
128
  extend_page 'hosts/_list' do |context|
129
129
  context.with_profile :cloud, _('RH Cloud'), default: true do
130
- add_pagelet :hosts_table_column_header, key: :insights_recommendations_count, label: _('Recommendations'), sortable: true, width: '12%', class: 'hidden-xs ellipsis', priority: 100
130
+ add_pagelet :hosts_table_column_header, key: :insights_recommendations_count, label: _('Recommendations'), sortable: true, width: '12%', class: 'hidden-xs ellipsis', priority: 100,
131
+ export_data: CsvExporter::ExportDefinition.new(:insights_recommendations_count, callback: ->(host) { host&.insights_hits&.count })
131
132
  add_pagelet :hosts_table_column_content, key: :insights_recommendations_count, callback: ->(host) { hits_counts_cell(host) }, class: 'hidden-xs ellipsis text-center', priority: 100
132
133
  end
133
134
  end
@@ -2,6 +2,7 @@ Foreman::SettingManager.define(:foreman) do
2
2
  category(:rh_cloud, N_('RHCloud')) do
3
3
  setting('allow_auto_inventory_upload', type: :boolean, description: N_('Enable automatic upload of your host inventory to the Red Hat cloud'), default: true, full_name: N_('Automatic inventory upload'))
4
4
  setting('allow_auto_insights_sync', type: :boolean, description: N_('Enable automatic synchronization of Insights recommendations from the Red Hat cloud'), default: false, full_name: N_('Synchronize recommendations Automatically'))
5
+ setting('allow_auto_insights_mismatch_delete', type: :boolean, description: N_('Enable automatic deletion of mismatched host records from the Red Hat cloud'), default: false, full_name: N_('Automatic mismatch deletion'))
5
6
  setting('obfuscate_inventory_hostnames', type: :boolean, description: N_('Obfuscate host names sent to the Red Hat cloud'), default: false, full_name: N_('Obfuscate host names'))
6
7
  setting('obfuscate_inventory_ips', type: :boolean, description: N_('Obfuscate ipv4 addresses sent to the Red Hat cloud'), default: false, full_name: N_('Obfuscate host ipv4 addresses'))
7
8
  setting('exclude_installed_packages', type: :boolean, description: N_('Exclude installed packages from being uploaded to the Red Hat cloud'), default: false, full_name: N_("Exclude installed Packages"))
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '7.0.46'.freeze
2
+ VERSION = '8.0.47'.freeze
3
3
  end
@@ -12,6 +12,7 @@ module InventorySync
12
12
  @sub_ids = result["results"].map { |host| host['subscription_manager_id'] }
13
13
  @uuid_by_sub_id = Hash[result["results"].map { |host| [host['subscription_manager_id'], host['id']] }]
14
14
  @uuid_by_fqdn = Hash[result["results"].map { |host| [host['fqdn']&.downcase, host['id']] }]
15
+ @results = result["results"]
15
16
  end
16
17
 
17
18
  def status_hashes
@@ -42,6 +43,10 @@ module InventorySync
42
43
  @host_uuids ||= Hash[@sub_ids.map { |sub_id| [host_id(sub_id), @uuid_by_sub_id[sub_id]] }].except(nil)
43
44
  end
44
45
 
46
+ def missing_hosts
47
+ @results.select { |host| hosts[host['subscription_manager_id']].nil? }
48
+ end
49
+
45
50
  def percentage
46
51
  ratio = @per_page * @page * 1.0 / @total * 100
47
52
  ratio > 100 ? 100 : ratio.truncate(2)
@@ -1,8 +1,11 @@
1
1
  module InventorySync
2
2
  module Async
3
3
  class InventoryHostsSync < QueryInventoryJob
4
+ MAX_IP_STRING_SIZE = 254
5
+
4
6
  set_callback :iteration, :around, :setup_facet_transaction
5
7
  set_callback :step, :around, :create_facets
8
+ set_callback :step, :around, :create_missing_hosts
6
9
 
7
10
  def plan(organizations)
8
11
  # by default the tasks will be executed concurrently
@@ -11,7 +14,7 @@ module InventorySync
11
14
  end
12
15
 
13
16
  def setup_facet_transaction
14
- InsightsFacet.transaction do
17
+ ActiveRecord::Base.transaction do
15
18
  yield
16
19
  end
17
20
  end
@@ -23,6 +26,17 @@ module InventorySync
23
26
  results
24
27
  end
25
28
 
29
+ def create_missing_hosts
30
+ results = yield
31
+ missing_hosts = results.missing_hosts.map { |host| to_missing_host_record(host, results.organization) }
32
+ # remove records that are no longer in the query results
33
+ InsightsMissingHost.
34
+ where.not(insights_id: missing_hosts.map { |host_hash| host_hash[:insights_id] }).
35
+ where(organization_id: results.organization.id).delete_all
36
+ # readd new hosts that appear in the results, but the subscription_id is missing from the DB.
37
+ InsightsMissingHost.upsert_all(missing_hosts) if missing_hosts.present?
38
+ end
39
+
26
40
  def rescue_strategy_for_self
27
41
  Dynflow::Action::Rescue::Fail
28
42
  end
@@ -42,6 +56,21 @@ module InventorySync
42
56
  InsightsFacet.upsert_all(all_facets, unique_by: :host_id) unless all_facets.empty?
43
57
  end
44
58
 
59
+ def to_missing_host_record(host_result, organization)
60
+ {
61
+ name: host_result['fqdn'],
62
+ insights_id: host_result['id'],
63
+ rhsm_id: host_result['subscription_manager_id'],
64
+ ip_address: to_ip_address_string(host_result['ip_addresses']),
65
+ organization_id: organization.id,
66
+ }
67
+ end
68
+
69
+ def to_ip_address_string(ip_addresses)
70
+ string_size = 0
71
+ ip_addresses.take_while { |address| (string_size += address.length) <= MAX_IP_STRING_SIZE }
72
+ end
73
+
45
74
  def plan_self_host_sync
46
75
  plan_action InventorySync::Async::InventorySelfHostSync
47
76
  end
@@ -14,8 +14,14 @@ module InventorySync
14
14
  end
15
15
 
16
16
  after_delay do
17
- Organization.unscoped.each do |org|
18
- plan_org_sync(org)
17
+ # perform a sequence of sync then delete in parallel for all organizations
18
+ concurrence do
19
+ Organization.unscoped.each do |org|
20
+ sequence do
21
+ plan_org_sync(org)
22
+ plan_remove_insights_hosts(org) if Setting[:allow_auto_insights_mismatch_delete]
23
+ end
24
+ end
19
25
  end
20
26
  end
21
27
  end
@@ -24,6 +30,11 @@ module InventorySync
24
30
  plan_action InventoryFullSync, org
25
31
  end
26
32
 
33
+ def plan_remove_insights_hosts
34
+ # plan a remove hosts action with search set to empty (all records)
35
+ plan_action(ForemanInventoryUpload::Async::RemoveInsightsHostsJob, '', org.id)
36
+ end
37
+
27
38
  def logger
28
39
  action_logger
29
40
  end
@@ -22,9 +22,9 @@ namespace :rh_cloud do |args|
22
22
  exit(1)
23
23
  end
24
24
 
25
- organization = Organization.find_by(id: ENV['org_id'].to_i) # saw this coming in as a string, so making sure it gets passed as an integer.
26
-
27
- @uid = cp_owner_id(organization)
25
+ @organization = Organization.find_by(id: ENV['org_id'].to_i) # saw this coming in as a string, so making sure it gets passed as an integer.
26
+ @uid = cp_owner_id(@organization)
27
+ @hostname = ForemanRhCloud.foreman_host_name
28
28
  logger.error('Organization provided does not have a manifest imported.') + exit(1) if @uid.nil?
29
29
 
30
30
  puts 'Paste your token, output will be hidden.'
@@ -39,7 +39,8 @@ namespace :rh_cloud do |args|
39
39
 
40
40
  def payload
41
41
  {
42
- "uid": @uid
42
+ "uid": @uid,
43
+ "display_name": "#{@hostname}+#{@organization.label}"
43
44
  }
44
45
  end
45
46
 
@@ -49,7 +50,7 @@ namespace :rh_cloud do |args|
49
50
 
50
51
  begin
51
52
  response = execute_cloud_request(
52
- organization: organization,
53
+ organization: @organization,
53
54
  method: method,
54
55
  url: registrations_url,
55
56
  headers: headers,
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "7.0.46",
3
+ "version": "8.0.47",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -110,7 +110,7 @@ module InsightsCloud::Api
110
110
  User.current = User.find_by(login: 'secret_admin')
111
111
 
112
112
  @env = FactoryBot.create(:katello_k_t_environment)
113
- cv = @env.content_views << FactoryBot.create(:katello_content_view, organization: @env.organization)
113
+ @env2 = FactoryBot.create(:katello_k_t_environment, organization: @env.organization)
114
114
 
115
115
  @host = FactoryBot.create(
116
116
  :host,
@@ -118,8 +118,16 @@ module InsightsCloud::Api
118
118
  :with_content,
119
119
  :with_hostgroup,
120
120
  :with_parameter,
121
- content_view: cv.first,
122
- lifecycle_environment: @env,
121
+ content_view_environments: [
122
+ FactoryBot.create(
123
+ :katello_content_view_environment,
124
+ content_view: FactoryBot.create(:katello_content_view, organization: @env.organization),
125
+ lifecycle_environment: @env),
126
+ FactoryBot.create(
127
+ :katello_content_view_environment,
128
+ content_view: FactoryBot.create(:katello_content_view, organization: @env.organization),
129
+ lifecycle_environment: @env2),
130
+ ],
123
131
  organization: @env.organization
124
132
  )
125
133
 
@@ -41,6 +41,14 @@ FactoryBot.define do
41
41
  resolution_risk { 1 }
42
42
  resolution_type { 'fix' }
43
43
  end
44
+
45
+ factory :insights_missing_host do
46
+ organization { association :organization }
47
+ sequence(:name) { |n| "removed.host#{n}.test" }
48
+ insights_id { Foreman.uuid }
49
+ rhsm_id { Foreman.uuid }
50
+ ip_address { "192.168.1.1,192.168.2.1" }
51
+ end
44
52
  end
45
53
 
46
54
  FactoryBot.modify do
@@ -33,6 +33,13 @@ FactoryBot.define do
33
33
  end
34
34
  end
35
35
 
36
+ FactoryBot.define do
37
+ factory :katello_content_view_environment, :class => Katello::ContentViewEnvironment do
38
+ sequence(:name) { |n| "name#{n}" }
39
+ sequence(:label) { |n| "label#{n}" }
40
+ end
41
+ end
42
+
36
43
  FactoryBot.define do
37
44
  factory :katello_k_t_environment, :aliases => [:katello_environment], :class => Katello::KTEnvironment do
38
45
  sequence(:name) { |n| "Environment#{n}" }
@@ -93,6 +100,7 @@ FactoryBot.modify do
93
100
  content_view { nil }
94
101
  lifecycle_environment { nil }
95
102
  content_source { nil }
103
+ content_view_environments { [] }
96
104
  end
97
105
 
98
106
  trait :with_content do
@@ -100,9 +108,14 @@ FactoryBot.modify do
100
108
 
101
109
  after(:build) do |host, evaluator|
102
110
  if host.content_facet
103
- host.content_facet.content_view = evaluator.content_view if evaluator.content_view
104
- host.content_facet.lifecycle_environment = evaluator.lifecycle_environment if evaluator.lifecycle_environment
111
+ if evaluator.content_view && evaluator.lifecycle_environment
112
+ host.content_facet.assign_single_environment(
113
+ content_view_id: evaluator.content_view.id,
114
+ lifecycle_environment_id: evaluator.lifecycle_environment.id
115
+ )
116
+ end
105
117
  host.content_facet.content_source = evaluator.content_source if evaluator.content_source
118
+ host.content_facet.content_view_environments = evaluator.content_view_environments unless evaluator.content_view_environments.empty?
106
119
  end
107
120
  end
108
121
  end
@@ -294,4 +294,36 @@ class InventoryHostsSyncTest < ActiveSupport::TestCase
294
294
 
295
295
  assert_nil @host2.insights
296
296
  end
297
+
298
+ test 'Inventory should create new missing host records' do
299
+ org = FactoryBot.create(:organization)
300
+
301
+ InventorySync::Async::InventoryHostsSync.any_instance.expects(:query_inventory).returns(@inventory)
302
+ InventorySync::Async::InventoryHostsSync.any_instance.expects(:plan_self_host_sync)
303
+
304
+ setup_certs_expectation do
305
+ InventorySync::Async::InventoryHostsSync.any_instance.stubs(:candlepin_id_cert)
306
+ end
307
+
308
+ ForemanTasks.sync_task(InventorySync::Async::InventoryHostsSync, [org])
309
+
310
+ assert_equal 2, InsightsMissingHost.count
311
+ end
312
+
313
+ test 'Inventory should remove old missing host records' do
314
+ org = FactoryBot.create(:organization)
315
+
316
+ InventorySync::Async::InventoryHostsSync.any_instance.expects(:query_inventory).returns(@inventory)
317
+ InventorySync::Async::InventoryHostsSync.any_instance.expects(:plan_self_host_sync)
318
+
319
+ FactoryBot.create(:insights_missing_host, organization: org)
320
+
321
+ setup_certs_expectation do
322
+ InventorySync::Async::InventoryHostsSync.any_instance.stubs(:candlepin_id_cert)
323
+ end
324
+
325
+ ForemanTasks.sync_task(InventorySync::Async::InventoryHostsSync, [org])
326
+
327
+ assert_equal 2, InsightsMissingHost.count
328
+ end
297
329
  end
@@ -6,8 +6,10 @@ class InventoryScheduledSyncTest < ActiveSupport::TestCase
6
6
 
7
7
  test 'Schedules an execution if auto upload is enabled' do
8
8
  Setting[:allow_auto_inventory_upload] = true
9
+ Setting[:allow_auto_insights_mismatch_delete] = true
9
10
 
10
11
  InventorySync::Async::InventoryScheduledSync.any_instance.expects(:plan_org_sync).times(Organization.unscoped.count)
12
+ InventorySync::Async::InventoryScheduledSync.any_instance.expects(:plan_remove_insights_hosts).times(Organization.unscoped.count)
11
13
 
12
14
  ForemanTasks.sync_task(InventorySync::Async::InventoryScheduledSync)
13
15
  end
@@ -19,4 +21,14 @@ class InventoryScheduledSyncTest < ActiveSupport::TestCase
19
21
 
20
22
  ForemanTasks.sync_task(InventorySync::Async::InventoryScheduledSync)
21
23
  end
24
+
25
+ test 'Skips mismatch deletion if the setting is disabled' do
26
+ Setting[:allow_auto_inventory_upload] = true
27
+ Setting[:allow_auto_insights_mismatch_delete] = false
28
+
29
+ InventorySync::Async::InventoryScheduledSync.any_instance.expects(:plan_org_sync).times(Organization.unscoped.count)
30
+ InventorySync::Async::InventoryScheduledSync.any_instance.expects(:plan_remove_insights_hosts).never
31
+
32
+ ForemanTasks.sync_task(InventorySync::Async::InventoryScheduledSync)
33
+ end
22
34
  end
@@ -0,0 +1,90 @@
1
+ require 'test_plugin_helper'
2
+ require 'foreman_tasks/test_helpers'
3
+
4
+ class RemoveInsightsHostJobTest < ActiveSupport::TestCase
5
+ include ForemanTasks::TestHelpers::WithInThreadExecutor
6
+
7
+ setup do
8
+ User.current = User.find_by(login: 'secret_admin')
9
+
10
+ Organization.any_instance.stubs(:manifest_expired?).returns(false)
11
+ @org = FactoryBot.create(:organization)
12
+ end
13
+
14
+ test 'Deletes host records on cloud success' do
15
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.expects(:execute_cloud_request).returns(
16
+ mock_response
17
+ )
18
+
19
+ FactoryBot.create(:insights_missing_host, organization: @org)
20
+
21
+ task = ForemanTasks.sync_task(ForemanInventoryUpload::Async::RemoveInsightsHostsJob, '', @org.id)
22
+
23
+ assert_equal 0, InsightsMissingHost.count
24
+ assert_equal 'success', task.result
25
+ end
26
+
27
+ test 'Does not delete hosts on cloud failure' do
28
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.expects(:execute_cloud_request).raises(
29
+ RestClient::Exceptions::EXCEPTIONS_MAP.fetch(500).new(mock_response(code: 500), 500)
30
+ )
31
+
32
+ FactoryBot.create(:insights_missing_host, organization: @org)
33
+
34
+ begin
35
+ ForemanTasks.sync_task(ForemanInventoryUpload::Async::RemoveInsightsHostsJob, '', @org.id)
36
+ rescue ForemanTasks::TaskError => ex
37
+ task = ex.task
38
+ end
39
+
40
+ assert_equal 1, InsightsMissingHost.count
41
+ assert_equal 'error', task.result
42
+ end
43
+
44
+ test 'Paginates the hosts list' do
45
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.stubs(:page_size).returns(1)
46
+
47
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.expects(:execute_cloud_request).returns(
48
+ mock_response(body: 'response2')
49
+ )
50
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.expects(:execute_cloud_request).returns(
51
+ mock_response(body: 'response1')
52
+ )
53
+
54
+ FactoryBot.create(:insights_missing_host, organization: @org)
55
+ FactoryBot.create(:insights_missing_host, organization: @org)
56
+
57
+ task = ForemanTasks.sync_task(ForemanInventoryUpload::Async::RemoveInsightsHostsJob, '', @org.id)
58
+
59
+ assert_equal 0, InsightsMissingHost.count
60
+ assert_equal 'success', task.result
61
+ assert_equal 'response1', task.output[:response_page1]
62
+ assert_equal 'response2', task.output[:response_page2]
63
+ end
64
+
65
+ test 'Uses scoped_search to select hosts' do
66
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.stubs(:page_size).returns(1)
67
+
68
+ # Since the request is paginated per 1 host, I would expect only one call to execute_cloud_request
69
+ ForemanInventoryUpload::Async::RemoveInsightsHostsJob.any_instance.expects(:execute_cloud_request).returns(
70
+ mock_response(body: 'response1')
71
+ )
72
+
73
+ FactoryBot.create(:insights_missing_host, name: 'test a', organization: @org)
74
+ FactoryBot.create(:insights_missing_host, name: 'test b', organization: @org)
75
+
76
+ task = ForemanTasks.sync_task(ForemanInventoryUpload::Async::RemoveInsightsHostsJob, 'name ~ b', @org.id)
77
+
78
+ assert_equal 1, InsightsMissingHost.count
79
+ assert_equal 'test a', InsightsMissingHost.first.name
80
+ assert_equal 'success', task.result
81
+ assert_equal 'response1', task.output[:response_page1]
82
+ end
83
+
84
+ def mock_response(code: 200, body: '')
85
+ response = mock('response')
86
+ response.stubs(:code).returns(code)
87
+ response.stubs(:body).returns(body)
88
+ response
89
+ end
90
+ end
@@ -8,6 +8,13 @@ FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories
8
8
  # FactoryBot.definition_file_paths << "#{Katello::Engine.root}/test/factories"
9
9
  FactoryBot.reload
10
10
 
11
+ begin
12
+ require 'pry-rescue/minitest'
13
+ require 'pry-byebug'
14
+ rescue LoadError
15
+ # if the extension is not loaded - please continue, no harm done.
16
+ end
17
+
11
18
  module FolderIsolation
12
19
  extend ActiveSupport::Concern
13
20
 
@@ -100,3 +107,13 @@ module MockForemanHostname
100
107
  end
101
108
  end
102
109
  end
110
+
111
+ module CandlepinIsolation
112
+ extend ActiveSupport::Concern
113
+
114
+ included do
115
+ setup do
116
+ Katello::Host::SubscriptionFacet.any_instance.stubs(:backend_update_needed?).returns(false)
117
+ end
118
+ end
119
+ end
@@ -2,6 +2,7 @@ require 'test_plugin_helper'
2
2
 
3
3
  class SliceGeneratorTest < ActiveSupport::TestCase
4
4
  include KatelloLocationFix
5
+ include CandlepinIsolation
5
6
 
6
7
  setup do
7
8
  User.current = User.find_by(login: 'secret_admin')
@@ -30,7 +31,7 @@ class SliceGeneratorTest < ActiveSupport::TestCase
30
31
  subscription: FactoryBot.create(:katello_subscription, organization_id: env.organization.id)
31
32
  )
32
33
  @host.interfaces.first.identifier = 'test_nic1'
33
- @host.save!
34
+ @host.interfaces.first.save!
34
35
 
35
36
  ForemanInventoryUpload::Generators::Queries.instance_variable_set(:@fact_names, nil)
36
37
  end
@@ -300,6 +301,11 @@ class SliceGeneratorTest < ActiveSupport::TestCase
300
301
 
301
302
  test 'generates a report with satellite facts' do
302
303
  hostgroup = FactoryBot.create(:hostgroup, name: 'Special"name')
304
+ # Don't try to update CP in tests
305
+ Katello::Resources::Candlepin::Consumer.stubs(:update)
306
+ # Don't try update facts for the host
307
+ Katello::Host::SubscriptionFacet.stubs(:update_facts)
308
+
303
309
  @host.hostgroup = hostgroup
304
310
  @host.save!
305
311
 
@@ -326,7 +332,7 @@ class SliceGeneratorTest < ActiveSupport::TestCase
326
332
  actual_host = actual['hosts'].first
327
333
  assert_tag('satellite-id', actual_host, 'satellite_instance_id')
328
334
  assert_tag(@host.organization_id.to_s, actual_host, 'organization_id')
329
- assert_tag(@host.content_view.name, actual_host, 'content_view')
335
+ assert_tag(@host.content_views.first.name, actual_host, 'content_view')
330
336
  assert_tag(@host.location.name, actual_host, 'location')
331
337
  assert_tag(@host.organization.name, actual_host, 'organization')
332
338
  assert_tag(@host.hostgroup.name, actual_host, 'hostgroup')
@@ -348,13 +354,13 @@ class SliceGeneratorTest < ActiveSupport::TestCase
348
354
  :host,
349
355
  :with_subscription,
350
356
  :with_content,
351
- content_view: @host.content_view,
352
- lifecycle_environment: @host.lifecycle_environment,
357
+ content_view: @host.content_views.first,
358
+ lifecycle_environment: @host.lifecycle_environments.first,
353
359
  organization: @host.organization
354
360
  )
355
361
 
356
362
  @host.subscription_facet.hypervisor_host = hypervisor_host
357
- @host.save!
363
+ @host.subscription_facet.save!
358
364
 
359
365
  batch = Host.where(id: @host.id).in_batches.first
360
366
  generator = create_generator(batch)
@@ -497,8 +503,8 @@ class SliceGeneratorTest < ActiveSupport::TestCase
497
503
  :host,
498
504
  :with_subscription,
499
505
  :with_content,
500
- content_view: @host.content_view,
501
- lifecycle_environment: @host.lifecycle_environment,
506
+ content_view: @host.content_views.first,
507
+ lifecycle_environment: @host.lifecycle_environments.first,
502
508
  organization: new_org
503
509
  )
504
510
 
@@ -644,8 +650,8 @@ class SliceGeneratorTest < ActiveSupport::TestCase
644
650
  :host,
645
651
  :with_subscription,
646
652
  :with_content,
647
- content_view: @host.content_view,
648
- lifecycle_environment: @host.lifecycle_environment,
653
+ content_view: @host.content_views.first,
654
+ lifecycle_environment: @host.lifecycle_environments.first,
649
655
  organization: @host.organization,
650
656
  installed_packages: [installed_package]
651
657
  )
@@ -2,12 +2,13 @@ require 'test_plugin_helper'
2
2
 
3
3
  class TagsGeneratorTest < ActiveSupport::TestCase
4
4
  include KatelloLocationFix
5
+ include CandlepinIsolation
5
6
 
6
7
  setup do
7
8
  User.current = User.find_by(login: 'secret_admin')
8
9
 
9
10
  env = FactoryBot.create(:katello_k_t_environment)
10
- cv = env.content_views << FactoryBot.create(:katello_content_view, organization: env.organization)
11
+ env2 = FactoryBot.create(:katello_k_t_environment, organization: env.organization)
11
12
 
12
13
  @location1 = FactoryBot.create(:location)
13
14
  @location2 = FactoryBot.create(:location, parent: @location1)
@@ -20,15 +21,27 @@ class TagsGeneratorTest < ActiveSupport::TestCase
20
21
  :redhat,
21
22
  :with_subscription,
22
23
  :with_content,
23
- content_view: cv.first,
24
- lifecycle_environment: env,
25
24
  organization: env.organization,
26
25
  location: @location2,
27
- hostgroup: @hostgroup2
26
+ hostgroup: @hostgroup2,
27
+ content_view_environments: [
28
+ FactoryBot.create(
29
+ :katello_content_view_environment,
30
+ content_view: FactoryBot.create(:katello_content_view, organization: env.organization),
31
+ lifecycle_environment: env),
32
+ FactoryBot.create(
33
+ :katello_content_view_environment,
34
+ content_view: FactoryBot.create(:katello_content_view, organization: env.organization),
35
+ lifecycle_environment: env2),
36
+ ]
28
37
  )
29
38
 
30
39
  @host.organization.pools << FactoryBot.create(:katello_pool, account_number: '1234', cp_id: 1)
31
40
  @host.interfaces.first.identifier = 'test_nic1'
41
+ # Don't try to update CP in tests
42
+ Katello::Resources::Candlepin::Consumer.stubs(:update)
43
+ # Don't try update facts for the host
44
+ Katello::Host::SubscriptionFacet.stubs(:update_facts)
32
45
  @host.save!
33
46
  end
34
47
 
@@ -46,8 +59,10 @@ class TagsGeneratorTest < ActiveSupport::TestCase
46
59
  assert_nil actual['host collection']
47
60
 
48
61
  assert_equal @host.organization.name, actual['organization'].first.last
49
- assert_equal @host.lifecycle_environment.name, actual['lifecycle_environment'].first.last
50
- assert_equal @host.content_view.name, actual['content_view'].first.last
62
+ assert_equal @host.lifecycle_environments.pluck(:name).min, actual['lifecycle_environment'].map(&:second).min
63
+ assert_equal @host.lifecycle_environments.pluck(:name).max, actual['lifecycle_environment'].map(&:second).max
64
+ assert_equal @host.content_views.pluck(:name).min, actual['content_view'].map(&:second).min
65
+ assert_equal @host.content_views.pluck(:name).max, actual['content_view'].map(&:second).max
51
66
  assert_equal Foreman.instance_id, actual['satellite_instance_id'].first.last
52
67
  assert_equal @host.organization_id.to_s, actual['organization_id'].first.last
53
68
  end
@@ -55,7 +70,7 @@ class TagsGeneratorTest < ActiveSupport::TestCase
55
70
  test 'filters tags with empty values' do
56
71
  generator = create_generator
57
72
 
58
- @host.stubs(:content_view)
73
+ @host.stubs(:content_views).returns([])
59
74
 
60
75
  actual = generator.generate.group_by { |key, value| key }
61
76
 
@@ -25,4 +25,11 @@ export const settingsDict = {
25
25
  'Exclude installed packages from being uploaded to the Red Hat cloud'
26
26
  ),
27
27
  },
28
+ allowAutoInsightsMismatchDelete: {
29
+ name: 'allow_auto_insights_mismatch_delete',
30
+ label: __('Automatic mismatch deletion'),
31
+ tooltip: __(
32
+ 'Enable automatic deletion of mismatched host records from the Red Hat cloud'
33
+ ),
34
+ },
28
35
  };
@@ -15,3 +15,6 @@ export const selectIpsObfuscationEnabled = state =>
15
15
 
16
16
  export const selectExcludePackages = state =>
17
17
  selectSettings(state).excludePackagesEnabled;
18
+
19
+ export const selectMismatchDelete = state =>
20
+ selectSettings(state).allowAutoInsightsMismatchDelete;
@@ -23,5 +23,9 @@ exports[`InventorySettings rendering render without Props 1`] = `
23
23
  key="excludePackagesEnabled"
24
24
  setting="excludePackagesEnabled"
25
25
  />
26
+ <AdvancedSetting
27
+ key="allowAutoInsightsMismatchDelete"
28
+ setting="allowAutoInsightsMismatchDelete"
29
+ />
26
30
  </div>
27
31
  `;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_rh_cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.46
4
+ version: 8.0.47
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-12 00:00:00.000000000 Z
11
+ date: 2023-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: katello
@@ -141,6 +141,7 @@ files:
141
141
  - app/models/insights_client_report_status.rb
142
142
  - app/models/insights_facet.rb
143
143
  - app/models/insights_hit.rb
144
+ - app/models/insights_missing_host.rb
144
145
  - app/models/insights_resolution.rb
145
146
  - app/models/insights_rule.rb
146
147
  - app/models/inventory_sync/inventory_status.rb
@@ -183,6 +184,7 @@ files:
183
184
  - db/migrate/20211027000001_create_task_output.foreman_rh_cloud.rb
184
185
  - db/migrate/20220321000001_add_unique_to_insights_rules.foreman_rh_cloud.rb
185
186
  - db/migrate/20221102110254_fix_rh_cloud_settings_category_to_dsl.rb
187
+ - db/migrate/20230515140211_add_missing_hosts_table.foreman_rh_cloud.rb
186
188
  - db/seeds.d/179_ui_notifications.rb
187
189
  - db/seeds.d/50_job_templates.rb
188
190
  - lib/foreman_inventory_upload.rb
@@ -192,6 +194,7 @@ files:
192
194
  - lib/foreman_inventory_upload/async/generate_report_job.rb
193
195
  - lib/foreman_inventory_upload/async/progress_output.rb
194
196
  - lib/foreman_inventory_upload/async/queue_for_upload_job.rb
197
+ - lib/foreman_inventory_upload/async/remove_insights_hosts_job.rb
195
198
  - lib/foreman_inventory_upload/async/shell_process.rb
196
199
  - lib/foreman_inventory_upload/async/upload_report_job.rb
197
200
  - lib/foreman_inventory_upload/generators/archived_report.rb
@@ -256,6 +259,7 @@ files:
256
259
  - test/jobs/inventory_hosts_sync_test.rb
257
260
  - test/jobs/inventory_scheduled_sync_test.rb
258
261
  - test/jobs/inventory_self_host_sync_test.rb
262
+ - test/jobs/remove_insights_hosts_job_test.rb
259
263
  - test/jobs/upload_report_job_test.rb
260
264
  - test/models/insights_client_report_status_test.rb
261
265
  - test/test_plugin_helper.rb
@@ -692,6 +696,7 @@ test_files:
692
696
  - test/jobs/inventory_hosts_sync_test.rb
693
697
  - test/jobs/inventory_scheduled_sync_test.rb
694
698
  - test/jobs/inventory_self_host_sync_test.rb
699
+ - test/jobs/remove_insights_hosts_job_test.rb
695
700
  - test/jobs/upload_report_job_test.rb
696
701
  - test/models/insights_client_report_status_test.rb
697
702
  - test/test_plugin_helper.rb