/*
This file is part of TALER
(C) 2014--2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as
published by the Free Software Foundation; either version 3, or
(at your option) any later version.
TALER is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with TALER; see the file COPYING. If not,
see
*/
/**
* @file taler-merchant-benchmark.c
* @brief benchmark the backend to evaluate performance
* @author Marcello Stanisci
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "taler_merchant_testing_lib.h"
#define PAYTO_I1 "payto://x-taler-bank/localhost/42"
/**
* Maximum length of an amount (value plus currency string) needed by the test.
* We have a 32-bit and a 64-bit value (~48 characters), plus the currency, plus
* some punctuation.
*/
#define MAX_AMOUNT_LEN (TALER_CURRENCY_LEN + 64)
/**
* Maximum length of an order JSON. Generously allocated.
*/
#define MAX_ORDER_LEN (MAX_AMOUNT_LEN * 4 + 2048)
/* Error codes. */
enum PaymentGeneratorError
{
PG_SUCCESS = 0,
PG_NO_SUBCOMMAND,
PG_BAD_OPTIONS,
PG_BAD_CONFIG_FILE,
PG_FAILED_CFG_CURRENCY,
PG_FAILED_TO_PREPARE_MERCHANT,
PG_FAILED_TO_PREPARE_BANK,
PG_FAILED_TO_LAUNCH_MERCHANT,
PG_FAILED_TO_LAUNCH_BANK,
PG_RUNTIME_FAILURE
};
/**
* ID to use for the 'alternative' instance.
*/
static const char *alt_instance_id = "alt";
/**
* What API key should we send in the HTTP 'Authorization' header?
*/
static char *apikey;
/**
* Witnesses if the ordinary cases payment suite should be run.
*/
static bool ordinary;
/**
* Witnesses if the corner cases payment suite should be run.
*/
static bool corner;
/**
* Base URL of the alternative non default instance.
*/
static char *alt_instance_url;
/**
* How many unaggregated payments we want to generate.
*/
static unsigned int unaggregated_number = 1;
/**
* How many payments that use two coins we want to generate.
*/
static unsigned int twocoins_number = 1;
/**
* How many payments we want to generate.
*/
static unsigned int payments_number = 1;
/**
* How many /tracks operation we want to perform.
*/
static unsigned int tracks_number = 1;
/**
* Skip instance creation?
*/
static int skip_instance = 0;
/**
* Config filename to give to commands (like wirewatch).
*/
static char *cfg_filename;
/**
* Bank configuration.
*/
static struct TALER_TESTING_BankConfiguration bc;
/**
* Merchant base URL.
*/
static char *merchant_url;
/**
* Currency used.
*/
static char *currency;
/**
* Actual commands collection.
*/
static void
run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
char CURRENCY_10_02[MAX_AMOUNT_LEN];
char CURRENCY_10[MAX_AMOUNT_LEN];
char CURRENCY_9_98[MAX_AMOUNT_LEN];
char CURRENCY_5_01[MAX_AMOUNT_LEN];
char CURRENCY_5[MAX_AMOUNT_LEN];
char CURRENCY_4_99[MAX_AMOUNT_LEN];
char CURRENCY_0_02[MAX_AMOUNT_LEN];
char CURRENCY_0_01[MAX_AMOUNT_LEN];
GNUNET_snprintf (CURRENCY_10_02,
sizeof (CURRENCY_10_02),
"%s:10.02",
currency);
GNUNET_snprintf (CURRENCY_10,
sizeof (CURRENCY_10),
"%s:10",
currency);
GNUNET_snprintf (CURRENCY_9_98,
sizeof (CURRENCY_9_98),
"%s:9.98",
currency);
GNUNET_snprintf (CURRENCY_5_01,
sizeof (CURRENCY_5_01),
"%s:5.01",
currency);
GNUNET_snprintf (CURRENCY_5,
sizeof (CURRENCY_5),
"%s:5",
currency);
GNUNET_snprintf (CURRENCY_4_99,
sizeof (CURRENCY_4_99),
"%s:4.99",
currency);
GNUNET_snprintf (CURRENCY_0_02,
sizeof (CURRENCY_0_02),
"%s:0.02",
currency);
GNUNET_snprintf (CURRENCY_0_01,
sizeof (CURRENCY_0_01),
"%s:0.01",
currency);
if (NULL != apikey)
{
char *hdr;
GNUNET_asprintf (&hdr,
"%s: %s",
MHD_HTTP_HEADER_AUTHORIZATION,
apikey);
GNUNET_assert (GNUNET_OK ==
GNUNET_CURL_append_header (is->ctx,
hdr));
GNUNET_free (hdr);
}
if (ordinary)
{
struct TALER_TESTING_Command ordinary_commands[] = {
TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
merchant_url,
"default",
PAYTO_I1,
"EUR",
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
CURRENCY_10_02,
&bc.exchange_auth,
bc.user43_payto),
TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
cfg_filename),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
CURRENCY_5,
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
"create-reserve-1",
CURRENCY_5,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1",
merchant_url,
MHD_HTTP_OK,
NULL, /* random order ID please */
GNUNET_TIME_UNIT_ZERO_ABS,
GNUNET_TIME_UNIT_FOREVER_ABS,
"EUR:5.0"),
TALER_TESTING_cmd_merchant_pay_order ("deposit-simple",
merchant_url,
MHD_HTTP_OK,
"create-proposal-1",
"withdraw-coin-1",
CURRENCY_5,
CURRENCY_4_99,
NULL),
TALER_TESTING_cmd_rewind_ip ("rewind-payments",
"create-reserve-1",
payments_number),
/* Next proposal-pay cycle will be used by /track CMDs
* and so it will not have to be looped over, only /track
* CMDs will have to. */
TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
merchant_url,
MHD_HTTP_OK,
"order-worth-5-track",
GNUNET_TIME_UNIT_ZERO_ABS,
GNUNET_TIME_UNIT_FOREVER_ABS,
"EUR:5.0"),
TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-2",
merchant_url,
MHD_HTTP_OK,
"create-proposal-2",
"withdraw-coin-2",
CURRENCY_5,
CURRENCY_4_99,
NULL),
/* /track/transaction over deposit-simple-2 */
TALER_TESTING_cmd_exec_aggregator ("aggregate-1",
cfg_filename),
TALER_TESTING_cmd_exec_transfer ("transfer-1",
cfg_filename),
TALER_TESTING_cmd_merchant_post_transfer (
"post-transfer-1",
&bc.exchange_auth,
bc.exchange_auth.wire_gateway_url,
merchant_url,
"EUR:4.98",
MHD_HTTP_OK,
"deposit-simple-2",
NULL),
TALER_TESTING_cmd_merchant_get_transfers ("track-transfer-1",
merchant_url,
bc.user42_payto,
MHD_HTTP_OK,
"post-transfer-1",
NULL),
TALER_TESTING_cmd_rewind_ip ("rewind-tracks",
"track-transfer-1",
tracks_number),
TALER_TESTING_cmd_end ()
};
TALER_TESTING_run (is,
&ordinary_commands[skip_instance]);
return;
}
if (corner) /* should never be 'false' here */
{
struct TALER_TESTING_Command corner_commands[] = {
TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
merchant_url,
"default",
PAYTO_I1,
"EUR",
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_merchant_post_instances ("instance-create-alt",
merchant_url,
alt_instance_id,
PAYTO_I1,
"EUR",
MHD_HTTP_NO_CONTENT),
TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
CURRENCY_5_01,
&bc.exchange_auth,
bc.user43_payto),
TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
cfg_filename),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
CURRENCY_5,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-unaggregated-proposal",
alt_instance_url,
MHD_HTTP_OK,
"order-worth-5-unaggregated",
GNUNET_TIME_UNIT_ZERO_ABS,
GNUNET_TIME_UNIT_FOREVER_ABS,
"EUR:5.0"),
TALER_TESTING_cmd_merchant_pay_order ("deposit-unaggregated",
merchant_url,
MHD_HTTP_OK,
"create-unaggregated-proposal",
"withdraw-coin-1",
CURRENCY_5,
CURRENCY_4_99,
NULL),
TALER_TESTING_cmd_rewind_ip ("rewind-unaggregated",
"create-reserve-1",
unaggregated_number),
TALER_TESTING_cmd_admin_add_incoming ("create-reserve-2",
CURRENCY_10_02,
&bc.exchange_auth,
bc.user43_payto),
TALER_TESTING_cmd_exec_wirewatch ("wirewatch-2",
cfg_filename),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
"create-reserve-2",
CURRENCY_5,
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-3",
"create-reserve-2",
CURRENCY_5,
MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_orders ("create-twocoins-proposal",
merchant_url,
MHD_HTTP_OK,
"order-worth-10-2coins",
GNUNET_TIME_UNIT_ZERO_ABS,
GNUNET_TIME_UNIT_FOREVER_ABS,
"EUR:10.0"),
TALER_TESTING_cmd_merchant_pay_order ("deposit-twocoins",
merchant_url,
MHD_HTTP_OK,
"create-twocoins-proposal",
"withdraw-coin-2;withdraw-coin-3",
CURRENCY_10,
CURRENCY_9_98,
NULL),
TALER_TESTING_cmd_exec_aggregator ("aggregate-twocoins",
cfg_filename),
TALER_TESTING_cmd_exec_transfer ("transfer-twocoins",
cfg_filename),
TALER_TESTING_cmd_rewind_ip ("rewind-twocoins",
"create-reserve-2",
twocoins_number),
TALER_TESTING_cmd_end ()
};
TALER_TESTING_run (is,
&corner_commands[skip_instance]);
return;
}
}
/**
* Send SIGTERM and wait for process termination.
*
* @param process process to terminate.
*/
static void
terminate_process (struct GNUNET_OS_Process *process)
{
GNUNET_OS_process_kill (process,
SIGTERM);
GNUNET_OS_process_wait (process);
GNUNET_OS_process_destroy (process);
}
/**
* The main function of the serve tool
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, or `enum PaymentGeneratorError` on error
*/
int
main (int argc,
char *const *argv)
{
char *loglev = NULL;
char *logfile = NULL;
char *exchange_account = NULL;
struct GNUNET_OS_Process *bankd;
struct GNUNET_OS_Process *merchantd;
struct GNUNET_GETOPT_CommandLineOption *options;
struct GNUNET_GETOPT_CommandLineOption root_options[] = {
GNUNET_GETOPT_option_cfgfile (&cfg_filename),
GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
GNUNET_GETOPT_option_help ("Runs benchmark logic against merchant backend. "
"Must be used with either 'ordinary' or 'corner' sub-commands."),
GNUNET_GETOPT_option_string ('l',
"logfile",
"LF",
"will log to file LF",
&logfile),
GNUNET_GETOPT_option_loglevel (&loglev),
GNUNET_GETOPT_OPTION_END
};
struct GNUNET_GETOPT_CommandLineOption corner_options[] = {
GNUNET_GETOPT_option_string ('l',
"logfile",
"LF",
"will log to file LF",
&logfile),
GNUNET_GETOPT_option_loglevel (&loglev),
GNUNET_GETOPT_option_cfgfile (&cfg_filename),
GNUNET_GETOPT_option_help ("Populate databases with corner case payments"),
GNUNET_GETOPT_option_uint ('u',
"unaggregated-number",
"UN",
"will generate UN unaggregated payments, defaults to 1",
&unaggregated_number),
GNUNET_GETOPT_option_flag ('s',
"skip-instance",
"skip creating the backend default instance (use if the instance already exists with the backend)",
&skip_instance),
GNUNET_GETOPT_option_uint ('t',
"two-coins",
"TC",
"will perform TC 2-coins payments, defaults to 1",
&twocoins_number),
GNUNET_GETOPT_option_mandatory (
GNUNET_GETOPT_option_string ('e',
"exchange-account",
"SECTION",
"configuration section specifying the exchange account to use, mandatory",
&exchange_account)),
GNUNET_GETOPT_option_string ('a',
"apikey",
"APIKEY",
"HTTP 'Authorization' header to send to the merchant",
&apikey),
GNUNET_GETOPT_OPTION_END
};
struct GNUNET_GETOPT_CommandLineOption ordinary_options[] = {
GNUNET_GETOPT_option_string ('l',
"logfile",
"LF",
"will log to file LF",
&logfile),
GNUNET_GETOPT_option_loglevel (&loglev),
GNUNET_GETOPT_option_cfgfile (&cfg_filename),
GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
GNUNET_GETOPT_option_help ("Generate Taler ordinary payments"
" to populate the databases"),
GNUNET_GETOPT_option_mandatory (
GNUNET_GETOPT_option_string ('e',
"exchange-account",
"SECTION",
"configuration section specifying the exchange account to use, mandatory",
&exchange_account)),
GNUNET_GETOPT_option_uint ('p',
"payments-number",
"PN",
"will generate PN payments, defaults to 1",
&payments_number),
GNUNET_GETOPT_option_string ('a',
"apikey",
"APIKEY",
"HTTP 'Authorization' header to send to the merchant",
&apikey),
GNUNET_GETOPT_option_uint ('t',
"tracks-number",
"TN",
"will perform TN /track operations, defaults to 1",
&tracks_number),
GNUNET_GETOPT_OPTION_END
};
const char *default_config_file;
default_config_file = GNUNET_OS_project_data_get ()->user_config_file;
options = root_options;
if (NULL != argv[1])
{
if (0 == strcmp ("ordinary", argv[1]))
{
ordinary = true;
options = ordinary_options;
}
if (0 == strcmp ("corner", argv[1]))
{
corner = true;
options = corner_options;
}
}
{
int result;
result = GNUNET_GETOPT_run ("taler-merchant-benchmark",
options,
argc,
argv);
if (GNUNET_SYSERR == result)
{
return PG_BAD_OPTIONS;
}
if (0 == result)
return PG_SUCCESS;
}
GNUNET_log_setup ("taler-merchant-benchmark",
loglev,
logfile);
if ( (! ordinary) &&
(! corner) )
{
TALER_LOG_ERROR ("Please use 'ordinary' or 'corner' subcommands.\n");
return PG_NO_SUBCOMMAND;
}
if (NULL == cfg_filename)
cfg_filename = (char *) default_config_file;
/* load currency from configuration */
{
struct GNUNET_CONFIGURATION_Handle *cfg;
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_load (cfg,
cfg_filename))
{
TALER_LOG_ERROR ("Could not parse configuration\n");
return PG_BAD_CONFIG_FILE;
}
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
¤cy))
{
TALER_LOG_ERROR ("Failed to read currency from configuration\n");
GNUNET_CONFIGURATION_destroy (cfg);
return PG_FAILED_CFG_CURRENCY;
}
GNUNET_CONFIGURATION_destroy (cfg);
}
/* prepare merchant and bank */
merchant_url = TALER_TESTING_prepare_merchant (cfg_filename);
if (NULL == merchant_url)
{
TALER_LOG_ERROR ("Failed to prepare for the merchant\n");
return PG_FAILED_TO_PREPARE_MERCHANT;
}
GNUNET_assert (0 < strlen (merchant_url));
GNUNET_assert (merchant_url[strlen(merchant_url)-1] == '/');
GNUNET_assert (0 < GNUNET_asprintf (&alt_instance_url,
"%sinstances/%s/",
merchant_url,
alt_instance_id));
if (GNUNET_OK !=
TALER_TESTING_prepare_bank (cfg_filename,
GNUNET_NO,
exchange_account,
&bc))
{
TALER_LOG_ERROR ("Failed to prepare for the bank\n");
return PG_FAILED_TO_PREPARE_BANK;
}
/* launch merchant and bank */
if (NULL == (merchantd = TALER_TESTING_run_merchant (cfg_filename,
merchant_url)))
{
TALER_LOG_ERROR ("Failed to launch the merchant\n");
return PG_FAILED_TO_LAUNCH_MERCHANT;
}
if (NULL == (bankd = TALER_TESTING_run_bank (cfg_filename,
bc.exchange_auth.wire_gateway_url)))
{
TALER_LOG_ERROR ("Failed to run the bank\n");
terminate_process (merchantd);
return PG_FAILED_TO_LAUNCH_BANK;
}
/* launch exchange and run benchmark */
{
int result;
result = TALER_TESTING_setup_with_exchange (&run,
NULL,
cfg_filename);
terminate_process (merchantd);
terminate_process (bankd);
return (GNUNET_OK == result) ? 0 : PG_RUNTIME_FAILURE;
}
}