Skip to content

Commit 4efbfaa

Browse files
authored
Merge pull request #3554 from rbrw/pdb-5126-add-query-origin
(PDB-5126) Add support for query "origin"
2 parents b5d6d1c + 0a36253 commit 4efbfaa

File tree

8 files changed

+359
-201
lines changed

8 files changed

+359
-201
lines changed

documentation/api/query/v4/index.markdown

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ general info about queries, see [our guide to query structure.][query]
3737
* `ast_only`: optional. A boolean value. When true, the query response will be the supplied
3838
`query` in AST, either exactly as supplied or translated from PQL. False by default.
3939

40+
* `origin`: optional. A string describing the source of the query. It
41+
can be anything, and will be reported in the log when PuppetDB is
42+
configured to log queries. Note that Puppet intends to use origin
43+
names beginning with `puppet:` for its own queries, so it is
44+
recommended that other clients choose something else.
45+
4046
### Response format
4147

4248
The response will be in `application/json`, and will contain a list of JSON

src/puppetlabs/puppetdb/http/handlers.clj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
(def global-engine-params
6969
"Parameters that should always be forwarded from the incoming query to
7070
the engine."
71-
[:optimize_drop_unused_joins :include_facts_expiration :explain])
71+
[:optimize_drop_unused_joins :include_facts_expiration :explain :origin])
7272

7373
(defn status-response
7474
"Executes `query` and if a result is found, calls `found-fn` with
@@ -148,7 +148,8 @@
148148
(def global-params {:optional ["optimize_drop_unused_joins"
149149
"include_facts_expiration"
150150
"include_package_inventory"
151-
"explain"]})
151+
"explain"
152+
"origin"]})
152153
(def paging-params {:optional paging/query-params})
153154
(def pretty-params {:optional ["pretty"]})
154155
(def typical-params (merge-param-specs global-params

src/puppetlabs/puppetdb/http/query.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
(s/optional-key :include_package_inventory) (s/maybe s/Bool)
4747
(s/optional-key :order_by) (s/maybe [[(s/one s/Keyword "field")
4848
(s/one (s/enum :ascending :descending) "order")]])
49+
(s/optional-key :origin) (s/maybe s/Str)
4950
(s/optional-key :distinct_resources) (s/maybe s/Bool)
5051
(s/optional-key :distinct_start_time) s/Any
5152
(s/optional-key :distinct_end_time) s/Any

src/puppetlabs/puppetdb/query_eng.clj

Lines changed: 185 additions & 162 deletions
Large diffs are not rendered by default.

src/puppetlabs/puppetdb/query_eng/engine.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@
417417
:subquery? false}))
418418

419419
(def fact-paths-query
420-
"Query for the resource-params query, mostly used as a subquery"
420+
"Query for the fact-paths query, mostly used as a subquery"
421421
(map->Query {::which-query :fact-paths
422422
:can-drop-unused-joins? true
423423
:projections {"type" {:type :string

src/puppetlabs/puppetdb/utils.clj

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
[java.net MalformedURLException URISyntaxException URL]
1818
[java.nio ByteBuffer CharBuffer]
1919
[java.nio.charset Charset CoderResult StandardCharsets]
20-
(java.util.concurrent ScheduledThreadPoolExecutor TimeUnit)))
20+
(java.util.concurrent ScheduledThreadPoolExecutor TimeUnit)
21+
(org.apache.log4j MDC)))
2122

2223
(defmacro with-captured-throw [& body]
2324
`(try [(do ~@body)] (catch Throwable ex# ex#)))
@@ -34,6 +35,36 @@
3435
(apply print args)
3536
(flush)))
3637

38+
(defmacro with-log-mdc
39+
"Establishes the MDC contexts given by the alternating-kvs key value
40+
pairs during the execution of the body, and ensures that the
41+
original values (if any) are always restored before returning."
42+
[alternating-kvs & body]
43+
;; For now, assume this isn't used in performance-critical code,
44+
;; i.e. within tight loops.
45+
(when-not (even? (count alternating-kvs))
46+
(throw (RuntimeException. "Odd number of MDC key value pairs")))
47+
(when-not (every? string? (take-nth 2 alternating-kvs))
48+
(throw (RuntimeException. "MDC keys are not all strings")))
49+
(loop [[k v & alternating-kvs] alternating-kvs
50+
expansion `(do ~@body)]
51+
(if-not k
52+
expansion
53+
(recur alternating-kvs
54+
;; We know k is a string, so it's fine to repeat ~k
55+
`(let [v# ~v]
56+
(if (nil? v#)
57+
~expansion
58+
(let [orig# (MDC/get ~k)]
59+
(try
60+
(MDC/put ~k v#)
61+
~expansion
62+
(finally
63+
;; After you put a nil value MDC/getContext crashes
64+
(if (nil? orig#)
65+
(MDC/remove ~k)
66+
(MDC/put ~k orig#)))))))))))
67+
3768
(defn flush-and-exit
3869
"Attempts to flush *out* and *err*, reporting any failures to *err*,
3970
if possible, and then invokes (System/exit status)."

test/puppetlabs/puppetdb/http/query_logging_test.clj

Lines changed: 111 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
(ns puppetlabs.puppetdb.http.query-logging-test
2-
(:require [clojure.test :refer :all]
3-
[clojure.string :as str]
4-
[puppetlabs.kitchensink.core :as kitchensink]
5-
[puppetlabs.puppetdb.testutils.http :refer [query-response]]
6-
[puppetlabs.puppetdb.testutils.db :refer [with-test-db *db*]]
7-
[puppetlabs.puppetdb.testutils.http :refer [call-with-http-app]]
8-
[puppetlabs.puppetdb.testutils.services :refer [call-with-puppetdb-instance
9-
create-temp-config
10-
*server*]]
11-
[puppetlabs.puppetdb.cli.services :as svcs]
12-
[puppetlabs.trapperkeeper.app :refer [get-service]]
13-
[puppetlabs.trapperkeeper.testutils.logging :as tk-log]))
2+
(:require
3+
[clojure.java.io :as io]
4+
[clojure.string :as str]
5+
[clojure.test :refer :all]
6+
[clojure.walk :refer [keywordize-keys]]
7+
[puppetlabs.kitchensink.core :as kitchensink]
8+
[puppetlabs.puppetdb.cheshire :as json]
9+
[puppetlabs.puppetdb.cli.services :as svcs]
10+
[puppetlabs.puppetdb.query-eng :as qeng]
11+
[puppetlabs.puppetdb.testutils.catalogs :refer [replace-catalog]]
12+
[puppetlabs.puppetdb.testutils.db :refer [with-test-db *db*]]
13+
[puppetlabs.puppetdb.testutils.http
14+
:refer [call-with-http-app query-response with-http-app*]]
15+
[puppetlabs.puppetdb.testutils.log :refer [notable-pdb-event?]]
16+
[puppetlabs.puppetdb.testutils.services
17+
:refer [call-with-puppetdb-instance create-temp-config *server*]]
18+
[puppetlabs.puppetdb.time :as time]
19+
[puppetlabs.puppetdb.utils :refer [println-err]]
20+
[puppetlabs.trapperkeeper.app :refer [get-service]]
21+
[puppetlabs.trapperkeeper.testutils.logging :as tk-log
22+
:refer [with-log-suppressed-unless-notable
23+
with-logged-event-maps
24+
with-logging-to-atom]])
25+
(:import
26+
(java.util UUID)))
1427

1528
(defn logs-include?
1629
"Returns true if only one instance of unique-msg is found in the log."
@@ -37,29 +50,60 @@
3750
(defn prep-logs [logs]
3851
(->> @logs (map :message) keep-only-pdbquery-logs))
3952

40-
(deftest setting-log-queries-triggers-ast-sql-logging
41-
(tk-log/with-logged-event-maps logs
42-
(tk-log/with-log-level "puppetlabs.puppetdb.query-eng" :debug
43-
(with-test-db
44-
(call-with-http-app
45-
(fn []
46-
;; make a couple http queries to trigger a debug AST and SQL log message for each
47-
(is (= 200 (:status (query-response :get "/v4" ["from" "nodes"]))))
48-
(is (= 200 (:status (query-response :get "/v4" ["from" "facts"]))))
53+
(def catalog-1
54+
(-> "puppetlabs/puppetdb/cli/export/tiny-catalog.json"
55+
io/resource slurp json/parse-string keywordize-keys))
56+
57+
(deftest queries-are-logged-when-log-queries-is-true
58+
(tk-log/with-log-level "puppetlabs.puppetdb.query-eng" :debug
59+
(with-test-db
60+
(replace-catalog catalog-1)
61+
(with-http-app* #(assoc % :log-queries true)
62+
(doseq [[query exp-ast exp-sql exp-origin]
63+
;; produce-streaming-body
64+
[[["/v4" ["from" "nodes"] {:origin "foo"}]
65+
"\"from\",\"nodes\""
66+
"latest_report_noop_pending"
67+
"foo"]
68+
[["/v4" ["from" "facts"]]
69+
"\"from\",\"facts\""
70+
"(jsonb_each((stable||volatile)))"
71+
nil]
72+
;; stream-query-result
73+
[["/v4/catalogs/myhost.localdomain" [] {:origin "bar"}]
74+
"\"from\",\"catalogs\""
75+
"row_to_json(edge_data)"
76+
"bar"]]]
77+
(with-logged-event-maps events
78+
(is (= 200 (:status (apply query-response :get query))))
79+
80+
(let [events @events
81+
;; Returns [everything uuid query-info]
82+
parse-event-msg #(->> % :message (re-find #"^PDBQuery:([^:]+):(.*)"))
83+
parse-event-info #(-> % parse-event-msg (nth 2) json/parse-string)
84+
uuid (some->> events (some parse-event-msg) second)
85+
qev-matching (fn [expected]
86+
(fn [{:keys [message] :as event}]
87+
(and (str/starts-with? message (str "PDBQuery:" uuid ":"))
88+
(str/includes? message expected))))]
4989

50-
(let [logs (prep-logs logs)]
51-
(testing "uuids match for the AST and SQL logged per query"
52-
(is (= [2 2] (vals (count-logs-uuids logs)))))
90+
(is (uuid? (UUID/fromString uuid)))
5391

54-
(testing "AST/SQL is logged for both queries above"
55-
;; match the AST/SQL logs for nodes query
56-
(is (logs-include? logs "\"from\" \"nodes\""))
57-
(is (logs-include? logs "latest_report_noop_pending"))
92+
(let [[ev & evs] (filter (qev-matching exp-ast) events)]
93+
(is (not (seq evs)))
94+
(when (seq evs)
95+
(println-err "Unexpected log:" events))
96+
(let [{:strs [ast origin] :as info} (parse-event-info ev)]
97+
(is ast)
98+
(is (= exp-origin origin))))
5899

59-
;; match the AST/SQL logs for facts query
60-
(is (logs-include? logs "\"from\" \"facts\""))
61-
(is (logs-include? logs "(jsonb_each((stable||volatile)))")))))
62-
#(assoc % :log-queries true))))))
100+
(let [[ev & evs] (filter (qev-matching exp-sql) events)]
101+
(is (not (seq evs)))
102+
(when (seq evs)
103+
(println-err "Unexpected log:" events))
104+
(let [{:strs [sql origin] :as info} (parse-event-info ev)]
105+
(is sql)
106+
(is (= exp-origin origin)))))))))))
63107

64108
(deftest no-queries-are-logged-when-log-queires-is-false
65109
(tk-log/with-logged-event-maps logs
@@ -92,11 +136,11 @@
92136

93137
(testing "AST/SQL is logged for both queries above"
94138
;; match the AST/SQL logs for nodes query
95-
(is (logs-include? logs "\"from\" \"nodes\""))
139+
(is (logs-include? logs "\"from\",\"nodes\""))
96140
(is (logs-include? logs "latest_report_noop_pending"))
97141

98142
;; match the AST/SQL logs for facts query
99-
(is (logs-include? logs "\"from\" \"facts\""))
143+
(is (logs-include? logs "\"from\",\"facts\""))
100144
(is (logs-include? logs "(jsonb_each((stable||volatile)))")))))))))))
101145

102146
(deftest no-PuppetDBServer-tk-service-queries-are-logged-when-log-queries-is-false
@@ -113,3 +157,36 @@
113157
(svcs/query pdb-service :v4 ["from" "facts"] nil identity)
114158
(svcs/query pdb-service :v4 ["from" "nodes"] nil identity)
115159
(is (empty? (prep-logs logs))))))))))
160+
161+
(deftest queries-have-expected-log-mdc
162+
;; For now, we assume that all log messages generated by the
163+
;; query-eng ns during this period will be from threads handling the
164+
;; query.
165+
(tk-log/with-log-level "puppetlabs.puppetdb.query-eng" :debug
166+
(with-log-suppressed-unless-notable notable-pdb-event?
167+
(with-test-db
168+
(let [context {:scf-read-db *db*
169+
:url-prefix "/pdb"
170+
:log-queries true
171+
:add-agent-report-filter true
172+
:node-purge-ttl (time/parse-period "14d")}]
173+
(replace-catalog catalog-1)
174+
(let [events (atom [])]
175+
(with-logging-to-atom "puppetlabs.puppetdb.query-eng" events
176+
(qeng/stream-query-result :v4
177+
["from" "catalogs"]
178+
{:origin "foo"}
179+
context)
180+
(doseq [event @events
181+
:let [mdc (.getMDCPropertyMap event)]]
182+
(is (= "foo" (get mdc "pdb-query-origin")))
183+
(is (uuid? (UUID/fromString (get mdc "pdb-query-id")))))))
184+
(let [events (atom [])]
185+
(with-logging-to-atom "puppetlabs.puppetdb.query-eng" events
186+
(qeng/produce-streaming-body :v4
187+
{:query ["from" "nodes"] :origin "foo"}
188+
context)
189+
(doseq [event @events
190+
:let [mdc (.getMDCPropertyMap event)]]
191+
(is (= "foo" (get mdc "pdb-query-origin")))
192+
(is (uuid? (UUID/fromString (get mdc "pdb-query-id"))))))))))))

test/puppetlabs/puppetdb/utils_test.clj

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
(:require [puppetlabs.puppetdb.utils :refer :all]
33
[clojure.test :refer :all]
44
[puppetlabs.puppetdb.testutils :as tu]
5-
[clojure.walk :as walk]))
5+
[clojure.walk :as walk])
6+
(:import
7+
(org.apache.log4j MDC)))
68

79
(deftest test-println-err
810
(is (= "foo\n"
@@ -17,6 +19,23 @@
1719
(is (= "foo bar"
1820
(tu/with-err-str (print-err "foo" "bar")))))
1921

22+
(deftest with-log-mdc-behavior
23+
(with-log-mdc ["foo" "bar"]
24+
(is (= "bar" (MDC/get "foo"))))
25+
(with-log-mdc ["foo" nil]
26+
(is (= {} (MDC/getContext))))
27+
(with-log-mdc ["foo" nil "bar" 1]
28+
(is (= {"bar" "1"} (MDC/getContext))))
29+
(with-log-mdc ["foo" 1 "bar" nil]
30+
(is (= {"foo" "1"} (MDC/getContext))))
31+
(with-log-mdc ["foo" "x" "bar" "y"]
32+
(is (= {"foo" "x" "bar" "y"} (MDC/getContext))))
33+
(with-log-mdc ["foo" "x" "bar" nil "baz" "z"]
34+
(is (= {"foo" "x" "baz" "z"} (MDC/getContext)))
35+
(with-log-mdc ["foo" "x" "bar" "y" "baz" "z"]
36+
(is (= {"foo" "x" "bar" "y" "baz" "z"} (MDC/getContext))))
37+
(is (= {"foo" "x" "baz" "z"} (MDC/getContext)))))
38+
2039
(deftest test-assoc-when
2140
(is (= {:a 1 :b 2}
2241
(assoc-when {:a 1 :b 2} :b 100)))

0 commit comments

Comments
 (0)