diff options
author | Jakub Hrozek <jhrozek@redhat.com> | 2017-01-13 09:31:03 +0100 |
---|---|---|
committer | Lukas Slebodnik <lslebodn@redhat.com> | 2017-03-14 13:31:47 +0100 |
commit | 91b0592cdab22915dff27ceae6d8e49c608aea4a (patch) | |
tree | 0dbfa2bb14a1c1a0fb23f38318ea103126d5cbe2 /src | |
parent | ca90f2102a43a3d49a2ef26610d7b4ff3062a823 (diff) | |
download | sssd-91b0592cdab22915dff27ceae6d8e49c608aea4a.tar.gz sssd-91b0592cdab22915dff27ceae6d8e49c608aea4a.tar.xz sssd-91b0592cdab22915dff27ceae6d8e49c608aea4a.zip |
TESTS: test the curl wrapper with a command-line tool
In order to test the curl integration code, this patch adds a
command-line tool and tests that it's possible to drive a conversation
with the secrets responder using the tool.
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/tests/intg/Makefile.am | 1 | ||||
-rw-r--r-- | src/tests/intg/config.py.m4 | 1 | ||||
-rw-r--r-- | src/tests/intg/test_secrets.py | 143 | ||||
-rw-r--r-- | src/tests/tcurl_test_tool.c | 230 |
4 files changed, 374 insertions, 1 deletions
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am index 64f90b490..1d36fa0d2 100644 --- a/src/tests/intg/Makefile.am +++ b/src/tests/intg/Makefile.am @@ -38,6 +38,7 @@ config.py: config.py.m4 -D "secdbpath=\`$(secdbpath)'" \ -D "libexecpath=\`$(libexecdir)'" \ -D "runstatedir=\`$(runstatedir)'" \ + -D "abs_builddir=\`$(abs_builddir)'" \ $< > $@ root: diff --git a/src/tests/intg/config.py.m4 b/src/tests/intg/config.py.m4 index 65e17e55a..841aae01f 100644 --- a/src/tests/intg/config.py.m4 +++ b/src/tests/intg/config.py.m4 @@ -15,3 +15,4 @@ MCACHE_PATH = "mcpath" SECDB_PATH = "secdbpath" LIBEXEC_PATH = "libexecpath" RUNSTATEDIR = "runstatedir" +ABS_BUILDDIR = "abs_builddir" diff --git a/src/tests/intg/test_secrets.py b/src/tests/intg/test_secrets.py index 36f43cd7a..cbc1a1f06 100644 --- a/src/tests/intg/test_secrets.py +++ b/src/tests/intg/test_secrets.py @@ -76,6 +76,7 @@ def create_sssd_secrets_fixture(request): for secdb_file in os.listdir(config.SECDB_PATH): os.unlink(config.SECDB_PATH + "/" + secdb_file) request.addfinalizer(sec_teardown) + return secpid @pytest.fixture @@ -102,13 +103,27 @@ def setup_for_secrets(request): return None +def get_secrets_socket(): + return os.path.join(config.RUNSTATEDIR, "secrets.socket") + + @pytest.fixture def secrets_cli(request): - sock_path = os.path.join(config.RUNSTATEDIR, "secrets.socket") + sock_path = get_secrets_socket() cli = SecretsLocalClient(sock_path=sock_path) return cli +@pytest.fixture +def curlwrap_tool(request): + curlwrap_path = os.path.join(config.ABS_BUILDDIR, + "..", "..", "..", "tcurl-test-tool") + if os.access(curlwrap_path, os.X_OK): + return curlwrap_path + + return None + + def test_crd_ops(setup_for_secrets, secrets_cli): """ Test that the basic Create, Retrieve, Delete operations work @@ -178,6 +193,132 @@ def test_crd_ops(setup_for_secrets, secrets_cli): assert str(err413.value).startswith("413") +def run_curlwrap_tool(args, exp_http_code): + cmd = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, _ = cmd.communicate() + + assert cmd.returncode == 0 + + out = out.decode('utf-8') + exp_http_code_str = "Request HTTP code: %d" % exp_http_code + assert exp_http_code_str in out + + return out + + +def test_curlwrap_crd_ops(setup_for_secrets, + curlwrap_tool): + """ + Test that the basic Create, Retrieve, Delete operations work using our + tevent libcurl code + """ + if not curlwrap_tool: + pytest.skip("The tcurl tool is not available, skipping test") + sock_path = get_secrets_socket() + + # listing an empty DB yields a 404 + run_curlwrap_tool([curlwrap_tool, + '-v', '-s', sock_path, + 'http://localhost/secrets/'], + 404) + + # listing a non-existent secret yields a 404 + run_curlwrap_tool([curlwrap_tool, + '-v', '-s', sock_path, + 'http://localhost/secrets/foo'], + 404) + + # set a secret foo:bar + run_curlwrap_tool([curlwrap_tool, '-p', + '-v', '-s', sock_path, + 'http://localhost/secrets/foo', + 'bar'], + 200) + + # list secrets + output = run_curlwrap_tool([curlwrap_tool, + '-v', '-s', sock_path, + 'http://localhost/secrets/'], + 200) + assert "foo" in output + + # get the foo secret + output = run_curlwrap_tool([curlwrap_tool, + '-v', '-s', sock_path, + 'http://localhost/secrets/foo'], + 200) + assert "bar" in output + + # Overwriting a secret is an error + run_curlwrap_tool([curlwrap_tool, '-p', + '-v', '-s', sock_path, + 'http://localhost/secrets/foo', + 'baz'], + 409) + + # Delete a secret + run_curlwrap_tool([curlwrap_tool, '-d', + '-v', '-s', sock_path, + 'http://localhost/secrets/foo'], + 200) + + # Delete a non-existent secret must yield a 404 + run_curlwrap_tool([curlwrap_tool, '-d', + '-v', '-s', sock_path, + 'http://localhost/secrets/foo'], + 404) + + +def test_curlwrap_parallel(setup_for_secrets, + curlwrap_tool): + """ + The tevent libcurl wrapper is meant to be non-blocking. Test + its operation in parallel. + """ + if not curlwrap_tool: + pytest.skip("The tcurl tool is not available, skipping test") + sock_path = get_secrets_socket() + + secrets = dict() + nsecrets = 10 + + for i in range(0, nsecrets): + secrets["key" + str(i)] = "value" + str(i) + + args = [curlwrap_tool, '-p', '-v', '-s', sock_path] + for skey, svalue in secrets.items(): + args.extend(['http://localhost/secrets/%s' % skey, svalue]) + run_curlwrap_tool(args, 200) + + output = run_curlwrap_tool([curlwrap_tool, + '-v', '-s', sock_path, + 'http://localhost/secrets/'], + 200) + for skey in secrets: + assert skey in output + + args = [curlwrap_tool, '-g', '-v', '-s', sock_path] + for skey in secrets: + args.extend(['http://localhost/secrets/%s' % skey]) + output = run_curlwrap_tool(args, 200) + + for svalue in secrets.values(): + assert svalue in output + + args = [curlwrap_tool, '-d', '-v', '-s', sock_path] + for skey in secrets: + args.extend(['http://localhost/secrets/%s' % skey]) + output = run_curlwrap_tool(args, 200) + + run_curlwrap_tool([curlwrap_tool, + '-v', '-s', sock_path, + 'http://localhost/secrets/'], + 404) + + def test_containers(setup_for_secrets, secrets_cli): """ Test that storing secrets inside containers works diff --git a/src/tests/tcurl_test_tool.c b/src/tests/tcurl_test_tool.c new file mode 100644 index 000000000..35ea97978 --- /dev/null +++ b/src/tests/tcurl_test_tool.c @@ -0,0 +1,230 @@ +/* + SSSD + + libcurl tevent integration test tool + + Copyright (C) Red Hat, 2016 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> + +#include "util/util.h" +#include "util/tev_curl.h" + +#define MAXREQ 64 + +struct tool_ctx { + bool verbose; + + errno_t error; + bool done; + + size_t nreqs; +}; + +static void request_done(struct tevent_req *req) +{ + int http_code; + struct sss_iobuf *outbuf; + struct tool_ctx *tool_ctx = tevent_req_callback_data(req, + struct tool_ctx); + + tool_ctx->error = tcurl_http_recv(tool_ctx, req, + &http_code, + &outbuf); + talloc_zfree(req); + + if (tool_ctx->error != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "HTTP request failed: %d\n", tool_ctx->error); + tool_ctx->done = true; + return; + } else if (tool_ctx->verbose) { + printf("Request HTTP code: %d\n", http_code); + printf("Request HTTP body: \n%s\n", + (const char *) sss_iobuf_get_data(outbuf)); + talloc_zfree(outbuf); + } + + tool_ctx->nreqs--; + if (tool_ctx->nreqs == 0) { + tool_ctx->done = true; + } +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + + int pc_debug = 0; + int pc_verbose = 0; + const char *socket_path = NULL; + const char *extra_arg_ptr; + + static const char *headers[] = { + "Content-type: application/octet-stream", + NULL, + }; + + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug", '\0', POPT_ARG_INT, &pc_debug, 0, + "The debug level to run with", NULL }, + { "socket-path", 's', POPT_ARG_STRING, &socket_path, 0, + "The path to the HTTP server socket", NULL }, + { "get", 'g', POPT_ARG_NONE, NULL, 'g', "Perform a HTTP GET (default)", NULL }, + { "put", 'p', POPT_ARG_NONE, NULL, 'p', "Perform a HTTP PUT", NULL }, + { "del", 'd', POPT_ARG_NONE, NULL, 'd', "Perform a HTTP DELETE", NULL }, + { "verbose", 'v', POPT_ARG_NONE, NULL, 'v', "Print response code and body", NULL }, + POPT_TABLEEND + }; + + struct tevent_req *req; + struct tevent_context *ev; + enum tcurl_http_request req_type = TCURL_HTTP_GET; + struct tcurl_ctx *ctx; + struct tool_ctx *tool_ctx; + + const char *urls[MAXREQ] = { 0 }; + struct sss_iobuf **inbufs; + + size_t n_reqs = 0; + + debug_prg_name = argv[0]; + pc = poptGetContext(NULL, argc, argv, long_options, 0); + poptSetOtherOptionHelp(pc, "HTTPDATA"); + + while ((opt = poptGetNextOpt(pc)) > 0) { + switch (opt) { + case 'g': + req_type = TCURL_HTTP_GET; + break; + case 'p': + req_type = TCURL_HTTP_PUT; + break; + case 'd': + req_type = TCURL_HTTP_DELETE; + break; + case 'v': + pc_verbose = 1; + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unexpected option\n"); + return 1; + } + } + + DEBUG_CLI_INIT(pc_debug); + + tool_ctx = talloc_zero(NULL, struct tool_ctx); + if (tool_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not init tool context\n"); + return 1; + } + + inbufs = talloc_zero_array(tool_ctx, struct sss_iobuf *, MAXREQ); + if (inbufs == NULL) { + talloc_zfree(tool_ctx); + return 1; + } + + while ((extra_arg_ptr = poptGetArg(pc)) != NULL) { + switch (req_type) { + case TCURL_HTTP_GET: + case TCURL_HTTP_DELETE: + urls[n_reqs++] = extra_arg_ptr; + break; + case TCURL_HTTP_PUT: + if (urls[n_reqs] == NULL) { + urls[n_reqs] = extra_arg_ptr; + } else { + inbufs[n_reqs] = sss_iobuf_init_readonly( + inbufs, + (uint8_t *) discard_const(extra_arg_ptr), + strlen(extra_arg_ptr)); + if (inbufs[n_reqs] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not init input buffer\n"); + talloc_zfree(tool_ctx); + return 1; + } + n_reqs++; + } + break; + } + } + + if (opt != -1) { + poptPrintUsage(pc, stderr, 0); + fprintf(stderr, "%s", poptStrerror(opt)); + talloc_zfree(tool_ctx); + return 1; + } + + if (!socket_path) { + DEBUG(SSSDBG_FATAL_FAILURE, "Please specify the socket path\n"); + poptPrintUsage(pc, stderr, 0); + talloc_zfree(tool_ctx); + return 1; + } + + tool_ctx->nreqs = n_reqs; + tool_ctx->verbose = !!pc_verbose; + + ev = tevent_context_init(tool_ctx); + if (ev == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not init tevent context\n"); + talloc_zfree(tool_ctx); + return 1; + } + + ctx = tcurl_init(tool_ctx, ev); + if (ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not init tcurl context\n"); + talloc_zfree(tool_ctx); + return 1; + } + + for (size_t i = 0; i < n_reqs; i++) { + req = tcurl_http_send(tool_ctx, ev, ctx, + req_type, + socket_path, + urls[i], + headers, + inbufs[i], + 10); + if (ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not create request\n"); + talloc_zfree(tool_ctx); + return 1; + } + tevent_req_set_callback(req, request_done, tool_ctx); + } + + while (tool_ctx->done == false) { + tevent_loop_once(ev); + } + + if (tool_ctx->nreqs > 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "The tool finished with some pending requests, fail!\n"); + talloc_zfree(tool_ctx); + return 1; + } + + talloc_free(tool_ctx); + poptFreeContext(pc); + return 0; +} |