-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix #36716 - Add check for missing product-content
source https://github.com/ATIX-AG/orcharhino-scripts/tree/main/find_missing_candlepin_product_contents
- Loading branch information
Showing
4 changed files
with
236 additions
and
0 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
definitions/checks/candlepin/product_repository_association.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
module Checks | ||
module Candlepin | ||
class ProductContentAssociation < ForemanMaintain::Check | ||
metadata do | ||
description 'Make sure Product to Repository association in Candlepin DB is complete' | ||
label :candlepin_prod_repo_assoc | ||
tags :post_upgrade | ||
for_feature :candlepin_database | ||
end | ||
|
||
def missing_cp_associations | ||
feature(:candlepin_database).query(<<~SQL) | ||
SELECT c.content_id, c.uuid, c.name | ||
FROM cp2_content c | ||
JOIN cp2_owner_content oc ON c.uuid=oc.content_uuid | ||
LEFT OUTER JOIN ( | ||
SELECT pc.content_uuid | ||
FROM cp2_products p | ||
JOIN cp2_owner_products op ON p.uuid=op.product_uuid | ||
JOIN cp2_product_content pc ON p.uuid=pc.product_uuid | ||
) x ON c.uuid = x.content_uuid | ||
WHERE x.content_uuid IS NULL | ||
SQL | ||
end | ||
|
||
def run | ||
missing = missing_cp_associations | ||
|
||
assert(missing.empty?, | ||
"Candlepin DB is missing some Product to Content associations!\n" \ | ||
"Found #{missing.length} content entries with missing product association.", | ||
:next_steps => [Procedures::Candlepin::ProductContentAssociation.new]) | ||
end | ||
end | ||
end | ||
end |
122 changes: 122 additions & 0 deletions
122
definitions/procedures/candlepin/product_content_association.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
module Procedures::Candlepin | ||
class ProductContentAssociation < ForemanMaintain::Procedure | ||
metadata do | ||
for_feature :candlepin_database | ||
description 'Reassociate Content to Product in CandlepinDB' | ||
end | ||
|
||
# returns a Hash of candlepin product cp_id keys with a Hash of queried values | ||
# consisting of the product's name and the number of associated content | ||
# e.g. { '<cp_id>' => { 'name' => '...', count => X, ..}, ... } | ||
def foreman_content_num_by_product | ||
feature(:foreman_database).query(<<~SQL).map { |e| [e['cp_id'], e] }.to_h | ||
SELECT p.cp_id as cp_id, p.name as name, COUNT(c.id) as count | ||
FROM katello_products p | ||
JOIN katello_product_contents pc ON p.id = pc.product_id | ||
JOIN katello_contents c ON pc.content_id = c.id | ||
GROUP BY p.cp_id, p.name | ||
SQL | ||
end | ||
|
||
# return Hash of query-result Hashes with the respective candlepin product_id as key | ||
# similar to foreman_content_num_by_product() | ||
def cp_content_count_by_product | ||
feature(:candlepin_database).query(<<~SQL).map { |e| [e['product_id'], e] }.to_h | ||
SELECT product.product_id, product.uuid, product.name, COUNT(content.content_id) | ||
FROM cp_pool pool | ||
JOIN cp2_products product ON pool.product_uuid = product.uuid | ||
LEFT JOIN cp2_product_content pc ON product.uuid = pc.product_uuid | ||
LEFT JOIN cp2_content content ON content.uuid = pc.content_uuid | ||
GROUP BY product.uuid | ||
SQL | ||
end | ||
|
||
# returns a set of cp2_content ids for given product_id | ||
def cp_product_content_ids(product_id) | ||
feature(:candlepin_database).query(<<~SQL).map { |e| e['content_id'] }.to_set | ||
SELECT content.content_id | ||
FROM cp_pool pool | ||
JOIN cp2_products product ON pool.product_uuid = product.uuid | ||
JOIN cp2_product_content pc ON product.uuid = pc.product_uuid | ||
JOIN cp2_content content ON content.uuid = pc.content_uuid | ||
WHERE product.product_id = '#{product_id}' | ||
SQL | ||
end | ||
|
||
# return Set of candlepin content ids from katello_content table | ||
# for candlepin product with cp_id | ||
def katello_content_ids(cp_id) | ||
feature(:foreman_database).query(<<~SQL).map { |e| e['cp_content_id'] }.to_set | ||
SELECT c.cp_content_id | ||
FROM katello_products p | ||
JOIN katello_product_contents pc ON p.id = pc.product_id | ||
JOIN katello_contents c ON pc.content_id = c.id | ||
WHERE p.cp_id = '#{cp_id}' | ||
SQL | ||
end | ||
|
||
def assemble_restore_commands(look_closer_products) | ||
commands = [] | ||
look_closer_products.each do |cp_id, product| | ||
puts "Process Product #{product['name'].inspect}" | ||
# get content_ids from candlepin and katello | ||
missing_ids = katello_content_ids(cp_id) - cp_product_content_ids(cp_id) | ||
|
||
missing_ids.each do |content_id| | ||
commands << create_new_association_sql_inserts(product['uuid'], content_id) | ||
|
||
# clear entity version of affected product to avoid versioning and convergence issues | ||
commands << 'UPDATE cp2_products SET entity_version = NULL ' \ | ||
"WHERE uuid = '#{product['uuid']}'" | ||
end | ||
end | ||
commands | ||
end | ||
|
||
# returns SQL-INSERT String to recreate missing associations | ||
def create_new_association_sql_inserts(product_uuid, content_id) | ||
missing = feature(:candlepin_database).query( | ||
"SELECT name, uuid FROM cp2_content WHERE content_id = '#{content_id}'" | ||
) | ||
insert_sql = [] | ||
missing.each do |content| | ||
puts " - repair missing: #{content['name'].inspect}" | ||
insert_sql << "(REPLACE(uuid_in((md5((random())::text))::cstring)::text, '-', '' )," \ | ||
' true,' \ | ||
" '#{product_uuid}'," \ | ||
" '#{content['uuid']}'," \ | ||
' NOW(), NOW())' | ||
end | ||
|
||
<<~SQL | ||
INSERT INTO cp2_product_content | ||
(id, enabled, product_uuid, content_uuid, created, updated) | ||
VALUES #{insert_sql.join(', ')} | ||
SQL | ||
end | ||
|
||
def run | ||
candlepin_content_num_by_product = cp_content_count_by_product | ||
look_closer_products = {} | ||
|
||
foreman_content_num_by_product.each do |product_id, foreman_product| | ||
next unless candlepin_content_num_by_product.key?(product_id) | ||
|
||
candlepin_product = candlepin_content_num_by_product[product_id] | ||
next unless foreman_product['count'] != candlepin_product['count'] | ||
|
||
look_closer_products[product_id] = candlepin_product | ||
end | ||
|
||
res = feature(:candlepin_database).psql(<<~SQL) | ||
BEGIN; | ||
#{assemble_restore_commands(look_closer_products).join(";\n")}; | ||
COMMIT; | ||
SQL | ||
|
||
if res.include? 'ERROR' | ||
warn! "Repairing Product-Content association in CandlepinDB failed. Please check the logs." | ||
end | ||
end | ||
end | ||
end |
33 changes: 33 additions & 0 deletions
33
test/definitions/checks/candlepin/product_repository_association_test.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
require 'test_helper' | ||
|
||
describe Checks::Candlepin::ProductContentAssociation do | ||
include DefinitionsTestHelper | ||
|
||
subject do | ||
Checks::Candlepin::ProductContentAssociation.new | ||
end | ||
|
||
it 'passes when nothing found' do | ||
assume_feature_present(:candlepin_database) do |db| | ||
db.any_instance.expects(:query).returns([]) | ||
end | ||
result = run_check(subject) | ||
assert result.success?, 'Check expected to succeed' | ||
end | ||
|
||
it 'fails when missing associations' do | ||
assume_feature_present(:candlepin_database) do |db| | ||
db.any_instance.expects(:query).returns([{ | ||
'content_id' => '123', | ||
'uuid' => 'feed', | ||
'name' => 'foo', | ||
}]) | ||
end | ||
result = run_check(subject) | ||
assert result.fail?, 'Check expected to fail' | ||
msg = "Candlepin DB is missing some Product to Content associations!\n" | ||
msg += 'Found 1 content entries with missing product association.' | ||
assert_match msg, result.output | ||
assert_equal [Procedures::Candlepin::ProductContentAssociation], subject.next_steps.map(&:class) | ||
end | ||
end |
45 changes: 45 additions & 0 deletions
45
test/definitions/procedures/candlepin/product_content_association_test.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
require 'test_helper' | ||
|
||
describe Procedures::Candlepin::ProductContentAssociation do | ||
include DefinitionsTestHelper | ||
|
||
subject do | ||
Procedures::Candlepin::ProductContentAssociation.new | ||
end | ||
|
||
it 'fixes missing association' do | ||
product_id = '12345' | ||
product_name = 'dummy' | ||
content_id = '67890' | ||
content_name = 'Missing Repo' | ||
content_uuid = 'dead' | ||
assume_feature_present(:candlepin_database) do |db| | ||
db.any_instance.expects(:query).with( | ||
"SELECT name, uuid FROM cp2_content WHERE content_id = '#{content_id}'" | ||
).once.returns([{ | ||
'name' => content_name, | ||
'uuid' => content_uuid, | ||
}]) | ||
db.any_instance.expects(:psql).once.returns("BEGIN | ||
INSERT 0 2 | ||
UPDATE 1 | ||
COMMIT | ||
") | ||
end | ||
|
||
subject.expects(:foreman_content_num_by_product).once.returns({ product_id => { | ||
'cp_id' => product_id, 'name' => product_name, 'count' => 1 | ||
} }) | ||
subject.expects(:cp_content_count_by_product).once.returns({ product_id => { | ||
'product_id' => product_id, 'uuid' => 'feed', 'name' => product_name, 'count' => 0 | ||
} }) | ||
subject.expects(:cp_product_content_ids).once.with(product_id).returns([].to_set) | ||
subject.expects(:katello_content_ids).once.with(product_id).returns([content_id].to_set) | ||
|
||
result = run_procedure(subject) | ||
assert result.success?, 'the procedure was expected to succeed' | ||
msg = "Process Product #{product_name.inspect}\n" | ||
msg += " - repair missing: #{content_name.inspect}\n" | ||
assert_stdout msg | ||
end | ||
end |