Skip to content

Commit d9d8c8b

Browse files
authored
Merge pull request #22 from PractiTest/issue_6221_FC_enhancement_instance_params
issue_6221_FC_enhancement_instance_params
2 parents 0c98f68 + d0faac1 commit d9d8c8b

18 files changed

+820
-623
lines changed

.idea/clojure-deps.xml

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/runConfigurations.xml

Lines changed: 0 additions & 10 deletions
This file was deleted.

deps.edn

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{:paths ["src" "test"]
2-
:deps {org.clojure/clojure {:mvn/version "1.10.3"}
3-
org.clojure/tools.cli {:mvn/version "1.0.194"}
4-
org.clojure/core.async {:mvn/version "1.2.603"}
5-
cheshire/cheshire {:mvn/version "5.10.0"}
6-
clj-http/clj-http {:mvn/version "3.12.0"}
7-
throttler/throttler {:mvn/version "1.0.0"}
8-
org.clojure/tools.logging {:mvn/version "1.1.0"}
9-
ch.qos.logback/logback-classic {:mvn/version "1.2.3"}
10-
clj-time/clj-time {:mvn/version "0.15.2"}}
2+
:deps {org.clojure/clojure {:mvn/version "1.10.3"}
3+
org.clojure/tools.cli {:mvn/version "1.0.194"}
4+
org.clojure/core.async {:mvn/version "1.2.603"}
5+
cheshire/cheshire {:mvn/version "5.10.0"}
6+
clj-http/clj-http {:mvn/version "3.12.0"}
7+
throttler/throttler {:mvn/version "1.0.0"}
8+
org.clojure/tools.logging {:mvn/version "1.1.0"}
9+
ch.qos.logback/logback-classic {:mvn/version "1.2.3"}
10+
clj-time/clj-time {:mvn/version "0.15.2"}}
11+
1112
:aliases
1213
{:dev {:extra-paths ["dev"]}
1314
:package {:extra-paths ["resources" "target/cljs/"]}

src/practitest_firecracker/api.clj

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
(ns practitest-firecracker.api
2+
(:require
3+
[clj-http.client :as http]
4+
[clojure.string :as string]
5+
[throttler.core :refer [fn-throttler]]
6+
[clojure.tools.logging :as log]
7+
[practitest-firecracker.const :refer :all]
8+
[practitest-firecracker.utils :refer [exit group-errors pformat]]))
9+
10+
(def backoff-timeout "Backoff timeout in seconds" 20)
11+
(def max-attempts "Number of attempts to run" 10)
12+
(def timeout-between-attempts "Timeout in seconds between attempts" 1)
13+
14+
(defn create-api-throttler [rate]
15+
(let [fn-th (fn-throttler rate :minute)]
16+
fn-th))
17+
18+
(defn build-uri [base-uri resource-uri-template & params]
19+
(apply format (str base-uri resource-uri-template) params))
20+
21+
(defn throw-api-exception [ex-info status body uri]
22+
(exit status (group-errors body)))
23+
24+
(defn api-call [{:keys [credentials uri method query-params form-params]}]
25+
(assert (not (and query-params form-params))
26+
"both `query-params` and `form-params` can't be specified")
27+
(loop [results []
28+
uri uri
29+
attempts max-attempts
30+
params (cond-> {:basic-auth credentials
31+
:throw-exceptions false
32+
:as :json}
33+
query-params (assoc :query-params (conj query-params {:source "firecracker" :firecracker-version fc-version}))
34+
form-params (assoc :form-params (conj form-params {:source "firecracker" :firecracker-version fc-version}) :content-type :json))]
35+
(if (nil? uri)
36+
results
37+
(let [{:keys [status body]} (method uri params)]
38+
(if (> attempts 0)
39+
(case status
40+
504 (do
41+
;; load balancer freaking out, lets try again
42+
(Thread/sleep (* timeout-between-attempts 1000))
43+
(recur results uri (dec attempts) params))
44+
(500 502 503) (do
45+
(log/warnf "%s responded with %s - %s more attempts" uri status attempts)
46+
(Thread/sleep (* timeout-between-attempts 1000))
47+
(recur results uri (dec attempts) params))
48+
429 (do
49+
(log/warnf "API rate limit reached, waiting for %s seconds" backoff-timeout)
50+
(Thread/sleep (* backoff-timeout 1000))
51+
(recur results uri (dec attempts) params))
52+
200 (let [data (:data body)]
53+
(recur (vec
54+
(if (sequential? data)
55+
(concat results data)
56+
(conj results data)))
57+
(get-in body [:links :next])
58+
attempts
59+
(dissoc params :query-params)))
60+
(throw-api-exception ex-info status body uri))
61+
(throw-api-exception ex-info status body uri))))))
62+
63+
(defn make-client [{:keys [email api-token api-uri max-api-rate]}]
64+
{:credentials [email api-token]
65+
:base-uri (str api-uri
66+
(if (string/ends-with? api-uri "/") "" "/")
67+
"api/v2")
68+
:max-api-rate-throttler (create-api-throttler max-api-rate)})
69+
70+
(defn ll-testset-instances [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] testset-id test-ids]
71+
(when display-action-logs (log/infof "get instances from testsets %s" testset-id))
72+
(let [uri (build-uri base-uri testset-instances-uri project-id)]
73+
(api-call {:credentials credentials
74+
:uri uri
75+
:method (max-api-rate-throttler http/get)
76+
:query-params (cond-> {:set-ids testset-id}
77+
test-ids (assoc :test-ids test-ids))})))
78+
79+
(defn ll-test-steps [{:keys [base-uri credentials max-api-rate-throttler]} project-id test-id]
80+
(let [uri (build-uri base-uri test-steps-uri project-id)]
81+
(api-call {:credentials credentials
82+
:uri uri
83+
:method (max-api-rate-throttler http/get)
84+
:query-params {:test-ids test-id}})))
85+
86+
(defn ll-create-test [{:keys [base-uri credentials max-api-rate-throttler]} project-id attributes steps]
87+
(let [uri (build-uri base-uri create-test-uri project-id)]
88+
(first
89+
(api-call {:credentials credentials
90+
:uri uri
91+
:method (max-api-rate-throttler http/post)
92+
:form-params {:data {:type "tests"
93+
:attributes attributes
94+
:steps {:data steps}}}}))))
95+
96+
(defn ll-create-testset [{:keys [base-uri credentials max-api-rate-throttler]} project-id attributes test-ids]
97+
(let [uri (build-uri base-uri create-testset-uri project-id)]
98+
(first
99+
(api-call {:credentials credentials
100+
:uri uri
101+
:method (max-api-rate-throttler http/post)
102+
:form-params {:data {:type "sets"
103+
:attributes attributes
104+
:instances {:test-ids test-ids}}}}))))
105+
106+
(defn make-instances [testset-tests testid-params]
107+
(for [[testset-id test-ids-num] testset-tests
108+
test-id-num test-ids-num
109+
index (range (last test-id-num))]
110+
{:type "instances"
111+
:attributes {:set-id testset-id
112+
:test-id (first test-id-num)
113+
:parameters (get testid-params (first test-id-num))}}))
114+
115+
(defn has-duplicates? [key runs]
116+
(let [grouped (group-by key (into [] runs))]
117+
(not (= (count runs) (count grouped)))))
118+
119+
(defn ll-create-instances [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] instances]
120+
(when display-action-logs (log/infof "create instances"))
121+
(let [uri (build-uri base-uri create-instance-uri project-id)]
122+
(api-call {:credentials credentials
123+
:uri uri
124+
:method (max-api-rate-throttler http/post)
125+
:form-params {:data instances}})))
126+
127+
(defn ll-create-run [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] instance-id attributes steps]
128+
(when display-action-logs (log/infof "create run for instance %s" instance-id))
129+
(let [uri (build-uri base-uri create-run-uri project-id)]
130+
(first
131+
(api-call {:credentials credentials
132+
:uri uri
133+
:method (max-api-rate-throttler http/post)
134+
:form-params {:data {:type "instances"
135+
:attributes (merge attributes {:instance-id instance-id})
136+
:steps {:data steps}}}}))))
137+
138+
(defn ll-create-runs [{:keys [base-uri credentials max-api-rate-throttler] :as client} [project-id display-action-logs] runs]
139+
(when display-action-logs (log/infof "create runs"))
140+
(let [uri (build-uri base-uri create-run-uri project-id)]
141+
(if (has-duplicates? :instance-id runs)
142+
(let [grouped (group-by :instance-id runs)
143+
duplicates (map first (vals (into {} (filter #(> (count (last %)) 1) grouped))))
144+
uniq (map first (vals (into {} (filter #(= (count (last %)) 1) grouped))))]
145+
(do (doall
146+
(for [run duplicates]
147+
(ll-create-run {:base-uri base-uri :credentials credentials :max-api-rate-throttler max-api-rate-throttler} [project-id display-action-logs] (:instance-id run) (:attributes run) (:steps run))))
148+
(ll-create-runs {:base-uri base-uri :credentials credentials :max-api-rate-throttler max-api-rate-throttler} [project-id display-action-logs] uniq)))
149+
(api-call {:credentials credentials
150+
:uri uri
151+
:method (max-api-rate-throttler http/post)
152+
:form-params {:data
153+
(reduce conj []
154+
(doall (for [run (into () runs)]
155+
{:type "instances"
156+
:attributes (assoc (:attributes run) :instance-id (:instance-id run))
157+
:steps {:data (reduce conj [] (:steps run))}})))}}))))
158+
159+
(defn ll-find-tests [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] name-list]
160+
(when display-action-logs (log/infof "searching for testsets"))
161+
(let [uri (build-uri base-uri bulk-list-tests-uri project-id)]
162+
(api-call {:credentials credentials
163+
:uri uri
164+
:method (max-api-rate-throttler http/post)
165+
:form-params {:data name-list}})))
166+
167+
(defn ll-find-test [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] name]
168+
(when display-action-logs (log/infof "searching for test %s" name))
169+
(let [uri (build-uri base-uri list-tests-uri project-id)]
170+
;; in case there are more than one test with this name, return the first one
171+
(first
172+
(api-call {:credentials credentials
173+
:uri uri
174+
:method (max-api-rate-throttler http/get)
175+
:query-params {:name_exact name}}))))
176+
177+
(defn ll-find-testset [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] name]
178+
(when display-action-logs (log/infof "searching for testset %s" name))
179+
(let [uri (build-uri base-uri list-testsets-uri project-id)]
180+
(first
181+
(api-call {:credentials credentials
182+
:uri uri
183+
:method (max-api-rate-throttler http/get)
184+
:query-params {:name_exact name}}))))
185+
186+
(defn ll-get-custom-field [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] cf-id]
187+
(when display-action-logs (log/infof "searching custom field %s" cf-id))
188+
(let [uri (build-uri base-uri custom-field-uri project-id cf-id)]
189+
(first
190+
(api-call {:credentials credentials
191+
:uri uri
192+
:method (max-api-rate-throttler http/get)}))))
193+
194+
(defn ll-update-custom-field [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] cf-id possible-values]
195+
(when display-action-logs (log/infof "update custom field %s to possible-values: %s" cf-id possible-values))
196+
(let [uri (build-uri base-uri custom-field-uri project-id cf-id)]
197+
(first
198+
(api-call {:credentials credentials
199+
:uri uri
200+
:method (max-api-rate-throttler http/put)
201+
:form-params {:data {:type "custom_field"
202+
:attributes {:possible-values possible-values}}}}))))
203+
204+
(defn ll-update-test [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] attributes steps cf-id]
205+
(when display-action-logs (log/infof "update test %s in custom field id %s" (:name attributes) cf-id))
206+
(let [uri (build-uri base-uri update-test-uri project-id cf-id)]
207+
(first
208+
(api-call {:credentials credentials
209+
:uri uri
210+
:method (max-api-rate-throttler http/put)
211+
:form-params {:data {:type "tests"
212+
:attributes attributes
213+
:steps {:data steps}}}}))))
214+
215+
(defn ll-update-testset [{:keys [base-uri credentials max-api-rate-throttler]} [project-id display-action-logs] attributes steps cf-id]
216+
(when display-action-logs (log/infof "update testset %s in field id %s" (:name attributes) cf-id))
217+
(let [uri (build-uri base-uri update-testset-uri project-id cf-id)]
218+
(first
219+
(api-call {:credentials credentials
220+
:uri uri
221+
:method (max-api-rate-throttler http/put)
222+
:form-params {:data {:type "sets"
223+
:attributes attributes
224+
:steps {:data steps}}}}))))

src/practitest_firecracker/cli.clj

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
(ns practitest-firecracker.cli
22
(:require
3-
[clojure.tools.cli :refer [parse-opts]]
4-
[clojure.java.io :refer [file reader]]
5-
[clojure.walk :refer [postwalk]]
6-
[clojure.string :as string]
7-
[cheshire.core :as json]
8-
[practitest-firecracker.query-dsl :refer [read-query]]))
3+
[clojure.tools.cli :as cli]
4+
[clojure.java.io :refer [file reader]]
5+
[clojure.tools.logging :as log]
6+
[clojure.walk :refer [postwalk]]
7+
[clojure.string :as string]
8+
[cheshire.core :as json]
9+
[practitest-firecracker.query-dsl :refer [read-query]]))
910

1011
(defn parse-additional-fields [v]
1112
(postwalk #(if (string? %) (read-query %) %)
@@ -50,7 +51,7 @@
5051
[nil "--additional-run-fields JSON"
5152
"JSON containing the fields that should be added when creating PractiTest Runs"
5253
:default {}
53-
:parse-fn parse-additional-fields]
54+
:parse-fn parse-additional-fields]
5455
;; [nil "--test-case-as-pt-test" :default true]
5556
[nil "--test-case-as-pt-test-step" :default true]
5657
#_[nil "--pt-test-name DSL"
@@ -103,7 +104,7 @@
103104
new-parsed-json))
104105

105106
(defn parse-args [args]
106-
(let [{:keys [options arguments errors summary]} (parse-opts args cli-options)
107+
(let [{:keys [options arguments errors summary]} (cli/parse-opts args cli-options)
107108
new-parsed-json (when (not (= (:config-path options) nil)) (parse-config-file options))
108109
options (merge options new-parsed-json)]
109110
(cond

src/practitest_firecracker/const.clj

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
(ns practitest-firecracker.const)
2+
3+
(def ^:const fc-version "2.1.1")
4+
5+
(def ^:const testset-instances-uri "/projects/%d/instances.json")
6+
(def ^:const test-uri "/projects/%d/tests/%d.json")
7+
(def ^:const test-steps-uri "/projects/%d/steps.json")
8+
(def ^:const create-test-uri "/projects/%d/tests.json")
9+
(def ^:const create-testset-uri "/projects/%d/sets.json")
10+
(def ^:const create-instance-uri "/projects/%d/instances.json")
11+
(def ^:const create-run-uri "/projects/%d/runs.json")
12+
(def ^:const update-test-uri "/projects/%d/tests/%d.json")
13+
(def ^:const update-testset-uri "/projects/%d/sets/%d.json")
14+
(def ^:const list-tests-uri "/projects/%d/tests.json")
15+
(def ^:const bulk-list-tests-uri "/projects/%d/tests/bulk_search.json")
16+
(def ^:const list-testsets-uri "/projects/%d/sets.json")
17+
(def ^:const custom-field-uri "/projects/%d/custom_fields/%d.json")
18+
19+
;; Used when we get testset instances for multiple test ids
20+
;; It's a GET request, so if we pass too many test IDs, we get the "URL too long" error
21+
;; 50 sounds like a good compromise.
22+
23+
(def ^:const max-test-ids-bucket-size 50)

src/practitest_firecracker/core.clj

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
(ns practitest-firecracker.core
22
(:require
33
[practitest-firecracker.cli :refer [parse-args]]
4-
[practitest-firecracker.practitest :refer [make-client
5-
make-runs
4+
[practitest-firecracker.practitest :refer [make-runs
65
create-testsets
76
group-tests
87
create-or-update-tests
98
create-instances
10-
create-runs
11-
fc-version]]
9+
create-runs]]
1210
[practitest-firecracker.parser.core :refer [send-directory parse-files]]
11+
[practitest-firecracker.api :refer [make-client]]
1312
[practitest-firecracker.utils :refer [exit]]
1413
[clojure.pprint :as pprint]
15-
[clojure.java.io :refer [file]]
16-
[clj-time.core :as t])
14+
[clojure.java.io :as io]
15+
[clj-time.core :as t]
16+
[clojure.tools.logging :as log]
17+
[practitest-firecracker.const :refer [fc-version]])
1718
(:gen-class))
1819

1920
(defmacro timef
@@ -30,7 +31,7 @@
3031
(exit (if ok? 0 1) exit-message)
3132
(let [client (make-client (select-keys options [:email :api-token :api-uri :max-api-rate]))
3233
directory (:reports-path options)
33-
dirs (when-not (nil? directory) (for [dir directory] (clojure.java.io/file dir)))
34+
dirs (when-not (nil? directory) (for [dir directory] (io/file dir)))
3435
parsed-dirs (when-not (nil? dirs) (for [dir (file-seq (first dirs))] (parse-files dir)))
3536
additional-reports (send-directory parsed-dirs (:test-case-as-pt-test-step options) (:multitestset options) (:testset-name options) false)
3637
start-time (t/now)]
@@ -46,17 +47,17 @@
4647
(pprint/pprint {"=============== args: ===============" args}))
4748

4849
"version"
49-
(println "Version: " fc-version)
50+
(log/info "Version: " fc-version)
5051

5152
"create-and-populate-testset"
5253
(do
54+
(log/info "Start Running Firecracker, Version: " fc-version)
5355
(timef
5456
"create-and-populate-testset"
5557
(-> (create-testsets client options additional-reports)
5658
(group-tests client options)
5759
(create-or-update-tests client options start-time)
5860
(create-instances client options start-time)
5961
(make-runs client options start-time)
60-
(create-runs client options start-time)
61-
))
62+
(create-runs client options start-time)))
6263
(exit 0 (format "Done"))))))))

0 commit comments

Comments
 (0)