|
| 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}}}})))) |
0 commit comments