foreman_rh_cloud 7.0.46 → 8.0.47

Sign up to get free protection for your applications and to get access to all the features.
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