Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_auditor_exchanges.c
21 : * @brief command for testing /exchanges of the auditor
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : #include "taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler_auditor_service.h"
28 : #include "taler_testing_lib.h"
29 : #include "taler_signatures.h"
30 : #include "backoff.h"
31 :
32 : /**
33 : * How long do we wait AT MOST when retrying?
34 : */
35 : #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
36 : GNUNET_TIME_UNIT_MILLISECONDS, 100)
37 :
38 :
39 : /**
40 : * How often do we retry before giving up?
41 : */
42 : #define NUM_RETRIES 5
43 :
44 :
45 : /**
46 : * State for a "deposit confirmation" CMD.
47 : */
48 : struct ExchangesState
49 : {
50 :
51 : /**
52 : * Exchanges handle while operation is running.
53 : */
54 : struct TALER_AUDITOR_ListExchangesHandle *leh;
55 :
56 : /**
57 : * Auditor connection.
58 : */
59 : struct TALER_AUDITOR_Handle *auditor;
60 :
61 : /**
62 : * Interpreter state.
63 : */
64 : struct TALER_TESTING_Interpreter *is;
65 :
66 : /**
67 : * Task scheduled to try later.
68 : */
69 : struct GNUNET_SCHEDULER_Task *retry_task;
70 :
71 : /**
72 : * How long do we wait until we retry?
73 : */
74 : struct GNUNET_TIME_Relative backoff;
75 :
76 : /**
77 : * Expected HTTP response code.
78 : */
79 : unsigned int expected_response_code;
80 :
81 : /**
82 : * URL of the exchange expected to be included in the response.
83 : */
84 : const char *exchange_url;
85 :
86 : /**
87 : * How often should we retry on (transient) failures?
88 : */
89 : unsigned int do_retry;
90 :
91 : };
92 :
93 :
94 : /**
95 : * Run the command.
96 : *
97 : * @param cls closure.
98 : * @param cmd the command to execute.
99 : * @param is the interpreter state.
100 : */
101 : static void
102 : exchanges_run (void *cls,
103 : const struct TALER_TESTING_Command *cmd,
104 : struct TALER_TESTING_Interpreter *is);
105 :
106 :
107 : /**
108 : * Task scheduled to re-try #exchanges_run.
109 : *
110 : * @param cls a `struct ExchangesState`
111 : */
112 : static void
113 0 : do_retry (void *cls)
114 : {
115 0 : struct ExchangesState *es = cls;
116 :
117 0 : es->retry_task = NULL;
118 0 : es->is->commands[es->is->ip].last_req_time
119 0 : = GNUNET_TIME_absolute_get ();
120 0 : exchanges_run (es,
121 : NULL,
122 : es->is);
123 0 : }
124 :
125 :
126 : /**
127 : * Callback to analyze the /exchanges response.
128 : *
129 : * @param cls closure.
130 : * @param hr HTTP response details
131 : * @param num_exchanges length of the @a ei array
132 : * @param ei array with information about the exchanges
133 : */
134 : static void
135 0 : exchanges_cb (void *cls,
136 : const struct TALER_AUDITOR_HttpResponse *hr,
137 : unsigned int num_exchanges,
138 : const struct TALER_AUDITOR_ExchangeInfo *ei)
139 : {
140 0 : struct ExchangesState *es = cls;
141 :
142 0 : es->leh = NULL;
143 0 : if (es->expected_response_code != hr->http_status)
144 : {
145 0 : if (0 != es->do_retry)
146 : {
147 0 : es->do_retry--;
148 0 : if ( (0 == hr->http_status) ||
149 0 : (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
150 0 : (MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
151 : {
152 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
153 : "Retrying list exchanges failed with %u/%d\n",
154 : hr->http_status,
155 : (int) hr->ec);
156 : /* on DB conflicts, do not use backoff */
157 0 : if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
158 0 : es->backoff = GNUNET_TIME_UNIT_ZERO;
159 : else
160 0 : es->backoff = GNUNET_TIME_randomized_backoff (es->backoff,
161 : MAX_BACKOFF);
162 0 : es->is->commands[es->is->ip].num_tries++;
163 0 : es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff,
164 : &do_retry,
165 : es);
166 0 : return;
167 : }
168 : }
169 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
170 : "Unexpected response code %u/%d to command %s in %s:%u\n",
171 : hr->http_status,
172 : (int) hr->ec,
173 : es->is->commands[es->is->ip].label,
174 : __FILE__,
175 : __LINE__);
176 0 : json_dumpf (hr->reply,
177 : stderr,
178 : 0);
179 0 : TALER_TESTING_interpreter_fail (es->is);
180 0 : return;
181 : }
182 0 : if (NULL != es->exchange_url)
183 : {
184 0 : unsigned int found = GNUNET_NO;
185 :
186 0 : for (unsigned int i = 0;
187 : i<num_exchanges;
188 0 : i++)
189 0 : if (0 == strcmp (es->exchange_url,
190 0 : ei[i].exchange_url))
191 0 : found = GNUNET_YES;
192 0 : if (GNUNET_NO == found)
193 : {
194 0 : TALER_LOG_ERROR ("Exchange '%s' doesn't exist at this auditor\n",
195 : es->exchange_url);
196 0 : TALER_TESTING_interpreter_fail (es->is);
197 0 : return;
198 : }
199 :
200 0 : TALER_LOG_DEBUG ("Exchange '%s' exists at this auditor!\n",
201 : es->exchange_url);
202 : }
203 0 : TALER_TESTING_interpreter_next (es->is);
204 : }
205 :
206 :
207 : /**
208 : * Run the command.
209 : *
210 : * @param cls closure.
211 : * @param cmd the command to execute.
212 : * @param is the interpreter state.
213 : */
214 : static void
215 0 : exchanges_run (void *cls,
216 : const struct TALER_TESTING_Command *cmd,
217 : struct TALER_TESTING_Interpreter *is)
218 : {
219 0 : struct ExchangesState *es = cls;
220 :
221 : (void) cmd;
222 0 : es->is = is;
223 0 : es->leh = TALER_AUDITOR_list_exchanges
224 : (is->auditor,
225 : &exchanges_cb,
226 : es);
227 :
228 0 : if (NULL == es->leh)
229 : {
230 0 : GNUNET_break (0);
231 0 : TALER_TESTING_interpreter_fail (is);
232 0 : return;
233 : }
234 0 : return;
235 : }
236 :
237 :
238 : /**
239 : * Free the state of a "exchanges" CMD, and possibly cancel a
240 : * pending operation thereof.
241 : *
242 : * @param cls closure, a `struct ExchangesState`
243 : * @param cmd the command which is being cleaned up.
244 : */
245 : static void
246 0 : exchanges_cleanup (void *cls,
247 : const struct TALER_TESTING_Command *cmd)
248 : {
249 0 : struct ExchangesState *es = cls;
250 :
251 0 : if (NULL != es->leh)
252 : {
253 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
254 : "Command %u (%s) did not complete\n",
255 : es->is->ip,
256 : cmd->label);
257 0 : TALER_AUDITOR_list_exchanges_cancel (es->leh);
258 0 : es->leh = NULL;
259 : }
260 0 : if (NULL != es->retry_task)
261 : {
262 0 : GNUNET_SCHEDULER_cancel (es->retry_task);
263 0 : es->retry_task = NULL;
264 : }
265 0 : GNUNET_free (es);
266 0 : }
267 :
268 :
269 : /**
270 : * Offer internal data to other commands.
271 : *
272 : * @param cls closure.
273 : * @param[out] ret set to the wanted data.
274 : * @param trait name of the trait.
275 : * @param index index number of the traits to be returned.
276 : * @return #GNUNET_OK on success
277 : */
278 : static int
279 0 : exchanges_traits (void *cls,
280 : const void **ret,
281 : const char *trait,
282 : unsigned int index)
283 : {
284 : (void) cls;
285 : (void) ret;
286 : (void) trait;
287 : (void) index;
288 : /* Must define this function because some callbacks
289 : * look for certain traits on _all_ the commands. */
290 0 : return GNUNET_SYSERR;
291 : }
292 :
293 :
294 : /**
295 : * Create a "list exchanges" command.
296 : *
297 : * @param label command label.
298 : * @param auditor auditor connection.
299 : * @param expected_response_code expected HTTP response code.
300 : * @return the command.
301 : */
302 : struct TALER_TESTING_Command
303 0 : TALER_TESTING_cmd_exchanges (const char *label,
304 : struct TALER_AUDITOR_Handle *auditor,
305 : unsigned int expected_response_code)
306 : {
307 : struct ExchangesState *es;
308 :
309 0 : es = GNUNET_new (struct ExchangesState);
310 0 : es->auditor = auditor;
311 0 : es->expected_response_code = expected_response_code;
312 :
313 : {
314 0 : struct TALER_TESTING_Command cmd = {
315 : .cls = es,
316 : .label = label,
317 : .run = &exchanges_run,
318 : .cleanup = &exchanges_cleanup,
319 : .traits = &exchanges_traits
320 : };
321 :
322 0 : return cmd;
323 : }
324 : }
325 :
326 :
327 : /**
328 : * Create a "list exchanges" command and check whether
329 : * a particular exchange belongs to the returned bundle.
330 : *
331 : * @param label command label.
332 : * @param expected_response_code expected HTTP response code.
333 : * @param exchange_url URL of the exchange supposed to
334 : * be included in the response.
335 : * @return the command.
336 : */
337 : struct TALER_TESTING_Command
338 0 : TALER_TESTING_cmd_exchanges_with_url (const char *label,
339 : unsigned int expected_response_code,
340 : const char *exchange_url)
341 : {
342 : struct ExchangesState *es;
343 :
344 0 : es = GNUNET_new (struct ExchangesState);
345 0 : es->expected_response_code = expected_response_code;
346 0 : es->exchange_url = exchange_url;
347 : {
348 0 : struct TALER_TESTING_Command cmd = {
349 : .cls = es,
350 : .label = label,
351 : .run = &exchanges_run,
352 : .cleanup = &exchanges_cleanup,
353 : .traits = &exchanges_traits
354 : };
355 :
356 0 : return cmd;
357 : }
358 : }
359 :
360 :
361 : /**
362 : * Modify an exchanges command to enable retries when we get
363 : * transient errors from the auditor.
364 : *
365 : * @param cmd a deposit confirmation command
366 : * @return the command with retries enabled
367 : */
368 : struct TALER_TESTING_Command
369 0 : TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd)
370 : {
371 : struct ExchangesState *es;
372 :
373 0 : GNUNET_assert (&exchanges_run == cmd.run);
374 0 : es = cmd.cls;
375 0 : es->do_retry = NUM_RETRIES;
376 0 : return cmd;
377 : }
378 :
379 :
380 : /* end of testing_auditor_api_cmd_exchanges.c */
|