Skip to content

Commit 7d47d13

Browse files
austbrbrw
authored andcommitted
(PDB-5792) Prevent duplicate catalogs
Add a migration to make the catalogs certname index unique. In order to prevent the constraint addition failing for users who do have duplicate catalogs, purge any duplicate catalogs. The latest catalog will be kept. If catalogs have the same producer_timestamp, the id column is used as a tiebreaker.
1 parent adf1ed7 commit 7d47d13

File tree

4 files changed

+80
-4
lines changed

4 files changed

+80
-4
lines changed

ext/test/upgrade-and-exit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,6 @@ psql -U puppetdb puppetdb -c 'select max(version) from schema_migrations;' \
8383
> "$tmpdir/out"
8484
cat "$tmpdir/out"
8585
# This must be updated every time we add a new migration
86-
grep -qE ' 82$' "$tmpdir/out"
86+
grep -qE ' 88$' "$tmpdir/out"
8787

8888
test ! -e "$PDBBOX"/var/mq-migrated

resources/ext/cli/delete-reports.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ chown "$pg_user:$pg_user" "$tmp_dir"
7878

7979
# Verify that the PuppetDB schema version it the expected value
8080
# so that we do not incorrectly delete the report data.
81-
expected_schema_ver=82
81+
expected_schema_ver=88
8282
su - "$pg_user" -s /bin/sh -c "$psql_cmd -p $pg_port -d $pdb_db_name -c 'COPY ( SELECT max(version) FROM schema_migrations ) TO STDOUT;' > $tmp_dir/schema_ver"
8383
actual_schema_ver="$(cat "$tmp_dir/schema_ver")"
8484
if test "$actual_schema_ver" -ne $expected_schema_ver; then

src/puppetlabs/puppetdb/scf/migrate.clj

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,20 @@
23012301
"CREATE INDEX reports_status_id_idx ON reports USING btree (status_id)"
23022302
"CREATE INDEX reports_tx_uuid_expr_idx ON reports USING btree (((transaction_uuid)::text))")))
23032303

2304+
(defn prevent-duplicate-catalogs
2305+
[]
2306+
(jdbc/do-commands
2307+
;; Clear any possible duplicates
2308+
["DELETE FROM catalogs c1 USING catalogs c2"
2309+
" WHERE c1.certname = c2.certname"
2310+
" AND (c1.producer_timestamp, c1.id) < (c2.producer_timestamp, c2.id)"]
2311+
2312+
;; Remove the old index
2313+
"DROP INDEX catalogs_certname_idx"
2314+
2315+
;; Create a unique constraint on the certname, which creates the unique index
2316+
"ALTER TABLE catalogs ADD CONSTRAINT catalogs_certname_idx UNIQUE (certname)"))
2317+
23042318
(def migrations
23052319
"The available migrations, as a map from migration version to migration function."
23062320
{00 require-schema-migrations-table
@@ -2366,7 +2380,8 @@
23662380
79 add-report-partition-indexes-on-certname-end-time
23672381
80 add-workspaces-tables
23682382
81 migrate-resource-events-to-declarative-partitioning
2369-
82 migrate-reports-to-declarative-partitioning})
2383+
82 migrate-reports-to-declarative-partitioning
2384+
88 prevent-duplicate-catalogs})
23702385
;; Make sure that if you change the structure of reports
23712386
;; or resource events, you also update the delete-reports
23722387
;; cli command.

test/puppetlabs/puppetdb/scf/migrate_test.clj

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
:refer [*db* clear-db-for-testing!
2222
schema-info-map diff-schema-maps with-test-db]]
2323
[puppetlabs.puppetdb.scf.hash :as shash]
24-
[puppetlabs.puppetdb.time :refer [now to-timestamp]]
24+
[puppetlabs.puppetdb.time :refer [now to-timestamp] :as t]
2525
[puppetlabs.puppetdb.scf.partitioning :as part]
2626
[clojure.string :as str])
2727
(:import (java.time ZoneId ZonedDateTime)
@@ -2153,3 +2153,64 @@
21532153
(update :index-diff set)
21542154
(update :constraint-diff set))]
21552155
(is (= expected-diff diff)))))))
2156+
2157+
(deftest migration-88-prevent-duplicate-catalogs
2158+
(testing "reports table declarative partitioning migration"
2159+
(jdbc/with-db-connection *db*
2160+
(clear-db-for-testing!)
2161+
(fast-forward-to-migration! 82)
2162+
(let [ts1 (to-timestamp (now))
2163+
ts2 (-> (now)
2164+
(t/plus (t/hours 1))
2165+
to-timestamp)
2166+
fake-hash (sutils/munge-hash-for-storage "0001")]
2167+
2168+
(jdbc/insert-multi! :certnames
2169+
[{:certname "host-1"}
2170+
{:certname "host-2"}])
2171+
2172+
(jdbc/insert-multi! :catalogs
2173+
[{:id 1 :hash fake-hash
2174+
:certname "host-1" :producer_timestamp ts1
2175+
:api_version 1 :catalog_version "one"}
2176+
{:id 2 :hash fake-hash
2177+
:certname "host-1" :producer_timestamp ts2
2178+
:api_version 1 :catalog_version "one"}
2179+
{:id 3 :hash fake-hash
2180+
:certname "host-1" :producer_timestamp ts2
2181+
:api_version 1 :catalog_version "one"}
2182+
{:id 4 :hash fake-hash
2183+
:certname "host-2" :producer_timestamp ts1
2184+
:api_version 1 :catalog_version "one"}])
2185+
(let [before-migration (schema-info-map *db*)
2186+
_ (apply-migration-for-testing! 88)
2187+
diff (-> (diff-schema-maps before-migration (schema-info-map *db*))
2188+
(update :index-diff set)
2189+
(update :constraint-diff set))]
2190+
(is (= {:index-diff
2191+
#{{:left-only {:unique? false}
2192+
:right-only {:unique? true}
2193+
:same {:index "catalogs_certname_idx"
2194+
:user "pdb_test"
2195+
:primary? false
2196+
:is_partial false
2197+
:functional? false
2198+
:type "btree"
2199+
:index_keys ["certname"]
2200+
:table "catalogs"
2201+
:schema "public"}}}
2202+
2203+
:table-diff nil
2204+
2205+
:constraint-diff
2206+
#{{:left-only nil
2207+
:right-only {:constraint_name "catalogs_certname_idx"
2208+
:table_name "catalogs"
2209+
:constraint_type "UNIQUE"
2210+
:initially_deferred "NO"
2211+
:deferrable? "NO"}
2212+
:same nil}}}
2213+
diff))
2214+
(is (= [{:id 3 :certname "host-1" :producer_timestamp ts2}
2215+
{:id 4 :certname "host-2" :producer_timestamp ts1}]
2216+
(query-to-vec "select id, certname, producer_timestamp from catalogs"))))))))

0 commit comments

Comments
 (0)