/*
Copyright (C) 2017 Red Hat
SSSD tests: Test KCM wait queue
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see .
*/
#include "config.h"
#include
#include
#include "util/util.h"
#include "util/util_creds.h"
#include "tests/cmocka/common_mock.h"
#include "tests/cmocka/common_mock_resp.h"
#include "responder/kcm/kcmsrv_pvt.h"
#define INVALID_ID -1
#define FAST_REQ_ID 0
#define SLOW_REQ_ID 1
#define FAST_REQ_DELAY 1
#define SLOW_REQ_DELAY 2
/* register_cli_protocol_version is required in test since it links with
* responder_common.c module
*/
struct cli_protocol_version *register_cli_protocol_version(void)
{
static struct cli_protocol_version responder_test_cli_protocol_version[] = {
{ 0, NULL, NULL }
};
return responder_test_cli_protocol_version;
}
struct timed_request_state {
struct tevent_context *ev;
struct resp_ctx *rctx;
struct kcm_ops_queue_ctx *qctx;
struct cli_creds *client;
int delay;
int req_id;
struct kcm_ops_queue_entry *queue_entry;
};
static void timed_request_start(struct tevent_req *subreq);
static void timed_request_done(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt);
static struct tevent_req *timed_request_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct resp_ctx *rctx,
struct kcm_ops_queue_ctx *qctx,
struct cli_creds *client,
int delay,
int req_id)
{
struct tevent_req *req;
struct tevent_req *subreq;
struct timed_request_state *state;
req = tevent_req_create(mem_ctx, &state, struct timed_request_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->rctx = rctx;
state->qctx = qctx;
state->client = client;
state->delay = delay;
state->req_id = req_id;
DEBUG(SSSDBG_TRACE_ALL, "Request %p with delay %d\n", req, delay);
subreq = kcm_op_queue_send(state, ev, qctx, client);
if (subreq == NULL) {
return NULL;
}
tevent_req_set_callback(subreq, timed_request_start, req);
return req;
}
static void timed_request_start(struct tevent_req *subreq)
{
struct timeval tv;
struct tevent_timer *timeout = NULL;
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct timed_request_state *state = tevent_req_data(req,
struct timed_request_state);
errno_t ret;
ret = kcm_op_queue_recv(subreq, state, &state->queue_entry);
talloc_zfree(subreq);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
tv = tevent_timeval_current_ofs(state->delay, 0);
timeout = tevent_add_timer(state->ev, state, tv, timed_request_done, req);
if (timeout == NULL) {
tevent_req_error(req, ENOMEM);
return;
}
return;
}
static void timed_request_done(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *pvt)
{
struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
DEBUG(SSSDBG_TRACE_ALL, "Request %p done\n", req);
tevent_req_done(req);
}
static errno_t timed_request_recv(struct tevent_req *req,
int *req_id)
{
struct timed_request_state *state = tevent_req_data(req,
struct timed_request_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
*req_id = state->req_id;
return EOK;
}
struct test_ctx {
struct resp_ctx *rctx;
struct kcm_ops_queue_ctx *qctx;
struct kcm_ctx *kctx;
struct tevent_context *ev;
int *req_ids;
int num_requests;
int finished_requests;
bool done;
errno_t error;
};
struct kcm_ctx *
mock_kctx(TALLOC_CTX *mem_ctx,
struct resp_ctx *rctx)
{
struct kcm_ctx *kctx;
kctx = talloc_zero(mem_ctx, struct kcm_ctx);
if (!kctx) {
return NULL;
}
kctx->rctx = rctx;
return kctx;
}
static int setup_kcm_queue(void **state)
{
struct test_ctx *tctx;
tctx = talloc_zero(NULL, struct test_ctx);
assert_non_null(tctx);
tctx->ev = tevent_context_init(tctx);
assert_non_null(tctx->ev);
tctx->rctx = mock_rctx(tctx, tctx->ev, NULL, NULL);
assert_non_null(tctx->rctx);
tctx->kctx = mock_kctx(tctx, tctx->rctx);
assert_non_null(tctx->kctx);
tctx->qctx = kcm_ops_queue_create(tctx->kctx, tctx->kctx);
assert_non_null(tctx->qctx);
*state = tctx;
return 0;
}
static int teardown_kcm_queue(void **state)
{
struct test_ctx *tctx = talloc_get_type(*state, struct test_ctx);
talloc_free(tctx);
return 0;
}
static void test_kcm_queue_done(struct tevent_req *req)
{
struct test_ctx *test_ctx = tevent_req_callback_data(req,
struct test_ctx);
int req_id = INVALID_ID;
test_ctx->error = timed_request_recv(req, &req_id);
talloc_zfree(req);
if (test_ctx->error != EOK) {
test_ctx->done = true;
return;
}
if (test_ctx->req_ids[test_ctx->finished_requests] != req_id) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Request %d finished, expected %d\n",
req_id, test_ctx->req_ids[test_ctx->finished_requests]);
test_ctx->error = EIO;
test_ctx->done = true;
return;
}
test_ctx->finished_requests++;
if (test_ctx->finished_requests == test_ctx->num_requests) {
test_ctx->done = true;
return;
}
}
/*
* Just make sure that a single pass through the queue works
*/
static void test_kcm_queue_single(void **state)
{
struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
struct tevent_req *req;
struct cli_creds client;
static int req_ids[] = { 0 };
client.ucred.uid = getuid();
client.ucred.gid = getgid();
req = timed_request_send(test_ctx,
test_ctx->ev,
test_ctx->rctx,
test_ctx->qctx,
&client, 1, 0);
assert_non_null(req);
tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
test_ctx->num_requests = 1;
test_ctx->req_ids = req_ids;
while (test_ctx->done == false) {
tevent_loop_once(test_ctx->ev);
}
assert_int_equal(test_ctx->error, EOK);
}
/*
* Test that multiple requests from the same ID wait for one another
*/
static void test_kcm_queue_multi_same_id(void **state)
{
struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
struct tevent_req *req;
struct cli_creds client;
/* The slow request will finish first because request from
* the same ID are serialized
*/
static int req_ids[] = { SLOW_REQ_ID, FAST_REQ_ID };
client.ucred.uid = getuid();
client.ucred.gid = getgid();
req = timed_request_send(test_ctx,
test_ctx->ev,
test_ctx->rctx,
test_ctx->qctx,
&client,
SLOW_REQ_DELAY,
SLOW_REQ_ID);
assert_non_null(req);
tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
req = timed_request_send(test_ctx,
test_ctx->ev,
test_ctx->rctx,
test_ctx->qctx,
&client,
FAST_REQ_DELAY,
FAST_REQ_ID);
assert_non_null(req);
tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
test_ctx->num_requests = 2;
test_ctx->req_ids = req_ids;
while (test_ctx->done == false) {
tevent_loop_once(test_ctx->ev);
}
assert_int_equal(test_ctx->error, EOK);
}
/*
* Test that multiple requests from different IDs don't wait for one
* another and can run concurrently
*/
static void test_kcm_queue_multi_different_id(void **state)
{
struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
struct tevent_req *req;
struct cli_creds client;
/* In this test, the fast request will finish sooner because
* both requests are from different IDs, allowing them to run
* concurrently
*/
static int req_ids[] = { FAST_REQ_ID, SLOW_REQ_ID };
client.ucred.uid = getuid();
client.ucred.gid = getgid();
req = timed_request_send(test_ctx,
test_ctx->ev,
test_ctx->rctx,
test_ctx->qctx,
&client,
SLOW_REQ_DELAY,
SLOW_REQ_ID);
assert_non_null(req);
tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
client.ucred.uid = getuid() + 1;
client.ucred.gid = getgid() + 1;
req = timed_request_send(test_ctx,
test_ctx->ev,
test_ctx->rctx,
test_ctx->qctx,
&client,
FAST_REQ_DELAY,
FAST_REQ_ID);
assert_non_null(req);
tevent_req_set_callback(req, test_kcm_queue_done, test_ctx);
test_ctx->num_requests = 2;
test_ctx->req_ids = req_ids;
while (test_ctx->done == false) {
tevent_loop_once(test_ctx->ev);
}
assert_int_equal(test_ctx->error, EOK);
}
int main(int argc, const char *argv[])
{
poptContext pc;
int opt;
int rv;
struct poptOption long_options[] = {
POPT_AUTOHELP
SSSD_DEBUG_OPTS
POPT_TABLEEND
};
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_kcm_queue_single,
setup_kcm_queue,
teardown_kcm_queue),
cmocka_unit_test_setup_teardown(test_kcm_queue_multi_same_id,
setup_kcm_queue,
teardown_kcm_queue),
cmocka_unit_test_setup_teardown(test_kcm_queue_multi_different_id,
setup_kcm_queue,
teardown_kcm_queue),
};
/* Set debug level to invalid value so we can decide if -d 0 was used. */
debug_level = SSSDBG_INVALID;
pc = poptGetContext(argv[0], argc, argv, long_options, 0);
while((opt = poptGetNextOpt(pc)) != -1) {
switch(opt) {
default:
fprintf(stderr, "\nInvalid option %s: %s\n\n",
poptBadOption(pc, 0), poptStrerror(opt));
poptPrintUsage(pc, stderr, 0);
return 1;
}
}
poptFreeContext(pc);
DEBUG_CLI_INIT(debug_level);
/* Even though normally the tests should clean up after themselves
* they might not after a failed run. Remove the old DB to be sure */
tests_set_cwd();
rv = cmocka_run_group_tests(tests, NULL, NULL);
return rv;
}