diff options
author | Ben Pfaff <blp@nicira.com> | 2009-10-26 15:04:05 -0700 |
---|---|---|
committer | Ben Pfaff <blp@nicira.com> | 2009-11-04 15:24:40 -0800 |
commit | f212909325be9bc7e296e1a32e2fc89694a0049f (patch) | |
tree | 27bc021a3aa57d60fc62a2d8bd94257cc01cfa4f /tests | |
parent | 1c617a495fdc6bb91e29bf00df4c837f63d63199 (diff) | |
download | openvswitch-f212909325be9bc7e296e1a32e2fc89694a0049f.tar.gz |
Implement JSON-RPC protocol.
Diffstat (limited to 'tests')
-rw-r--r-- | tests/automake.mk | 5 | ||||
-rw-r--r-- | tests/jsonrpc.at | 45 | ||||
-rw-r--r-- | tests/test-json.c | 2 | ||||
-rw-r--r-- | tests/test-jsonrpc.c | 323 | ||||
-rw-r--r-- | tests/testsuite.at | 1 |
5 files changed, 375 insertions, 1 deletions
diff --git a/tests/automake.mk b/tests/automake.mk index a9edee0e5..628451125 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -12,6 +12,7 @@ TESTSUITE_AT = \ tests/aes128.at \ tests/uuid.at \ tests/json.at \ + tests/jsonrpc.at \ tests/timeval.at \ tests/lockfile.at \ tests/stp.at \ @@ -76,6 +77,10 @@ noinst_PROGRAMS += tests/test-json tests_test_json_SOURCES = tests/test-json.c tests_test_json_LDADD = lib/libopenvswitch.a +noinst_PROGRAMS += tests/test-jsonrpc +tests_test_jsonrpc_SOURCES = tests/test-jsonrpc.c +tests_test_jsonrpc_LDADD = lib/libopenvswitch.a + noinst_PROGRAMS += tests/test-list tests_test_list_SOURCES = tests/test-list.c tests_test_list_LDADD = lib/libopenvswitch.a diff --git a/tests/jsonrpc.at b/tests/jsonrpc.at new file mode 100644 index 000000000..d5ebf948a --- /dev/null +++ b/tests/jsonrpc.at @@ -0,0 +1,45 @@ +AT_BANNER([JSON-RPC]) + +AT_SETUP([JSON-RPC request and successful reply]) +AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket]) +AT_CHECK([test -s pid]) +AT_CHECK([kill -0 `cat pid`]) +AT_CHECK( + [[test-jsonrpc request unix:socket echo '[{"a": "b", "x": null}]']], [0], + [[{"error":null,"id":0,"result":[{"a":"b","x":null}]} +]], [ignore], [test ! -e pid || kill `cat pid`]) +AT_CHECK([kill `cat pid`]) +AT_CLEANUP + +AT_SETUP([JSON-RPC request and error reply]) +AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket]) +AT_CHECK([test -s pid]) +AT_CHECK([kill -0 `cat pid`]) +AT_CHECK( + [[test-jsonrpc request unix:socket bad-request '[]']], [0], + [[{"error":{"error":"unknown method"},"id":0,"result":null} +]], [ignore], [test ! -e pid || kill `cat pid`]) +AT_CHECK([kill `cat pid`]) +AT_CLEANUP + +AT_SETUP([JSON-RPC notification]) +AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket]) +AT_CHECK([test -s pid]) +# When a daemon dies it deletes its pidfile, so make a copy. +AT_CHECK([cp pid pid2]) +AT_CHECK([kill -0 `cat pid2`]) +OVS_CHECK_LCOV([[test-jsonrpc notify unix:socket shutdown '[]']], [0], [], + [ignore], [kill `cat pid2`]) +AT_CHECK( + [pid=`cat pid2` + # First try a quick sleep, so that the test completes very quickly + # in the normal case. POSIX doesn't require fractional times to + # work, so this might not work. + sleep 0.1; if kill -0 $pid; then :; else echo success; exit 0; fi + # Then wait up to 2 seconds. + sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi + sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi + echo failure; exit 1], [0], [success +], [ignore]) +AT_CHECK([test ! -e pid]) +AT_CLEANUP diff --git a/tests/test-json.c b/tests/test-json.c index bb9fadb06..6261786db 100644 --- a/tests/test-json.c +++ b/tests/test-json.c @@ -92,7 +92,7 @@ parse_multiple(const char *input_file) parser = json_parser_create(0); } - used = n - json_parser_feed(parser, &buffer[used], n - used); + used += json_parser_feed(parser, &buffer[used], n - used); if (used < n) { if (!print_and_free_json(json_parser_finish(parser))) { ok = false; diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c new file mode 100644 index 000000000..7f2166f4e --- /dev/null +++ b/tests/test-jsonrpc.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include "jsonrpc.h" + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> + +#include "command-line.h" +#include "daemon.h" +#include "json.h" +#include "poll-loop.h" +#include "stream.h" +#include "timeval.h" +#include "util.h" +#include "vlog.h" + +static struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + DAEMON_LONG_OPTIONS, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + for (;;) { + int c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'h': + usage(); + + case 'v': + vlog_set_verbosity(optarg); + break; + + DAEMON_OPTION_HANDLERS + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: JSON-RPC test utility\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n" + " listen LOCAL listen for connections on LOCAL\n" + " request REMOTE METHOD PARAMS send request, print reply\n" + " notify REMOTE METHOD PARAMS send notification and exit\n", + program_name, program_name); + stream_usage("JSON-RPC", true, true); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n"); + exit(EXIT_SUCCESS); +} + +/* Command helper functions. */ + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static void +print_and_free_json(struct json *json) +{ + char *string = json_to_string(json, JSSF_SORT); + json_destroy(json); + puts(string); + free(string); +} + +/* Command implementations. */ + +static void +handle_rpc(struct jsonrpc *rpc, struct jsonrpc_msg *msg, bool *done) +{ + struct jsonrpc_msg *reply = NULL; + if (msg->type == JSONRPC_REQUEST) { + if (!strcmp(msg->method, "echo")) { + reply = jsonrpc_create_reply(json_clone(msg->params), msg->id); + } else { + struct json *error = json_object_create(); + json_object_put_string(error, "error", "unknown method"); + reply = jsonrpc_create_error(error, msg->id); + ovs_error(0, "unknown request %s", msg->method); + } + + } else if (msg->type == JSONRPC_NOTIFY) { + if (!strcmp(msg->method, "shutdown")) { + *done = true; + } else { + jsonrpc_error(rpc, ENOTTY); + ovs_error(0, "unknown notification %s", msg->method); + } + } else { + jsonrpc_error(rpc, EPROTO); + ovs_error(0, "unsolicited JSON-RPC reply or error"); + } + + if (reply) { + jsonrpc_send(rpc, reply); + } +} + +static void +do_listen(int argc UNUSED, char *argv[]) +{ + struct pstream *pstream; + struct jsonrpc **rpcs; + size_t n_rpcs, allocated_rpcs; + bool done; + int error; + + die_if_already_running(); + + error = pstream_open(argv[1], &pstream); + if (error) { + ovs_fatal(error, "could not listen on \"%s\"", argv[1]); + } + + daemonize(); + + rpcs = NULL; + n_rpcs = allocated_rpcs = 0; + done = false; + for (;;) { + struct stream *stream; + size_t i; + + /* Accept new connections. */ + error = pstream_accept(pstream, &stream); + if (!error) { + if (n_rpcs >= allocated_rpcs) { + rpcs = x2nrealloc(rpcs, &allocated_rpcs, sizeof *rpcs); + } + rpcs[n_rpcs++] = jsonrpc_open(stream); + } else if (error != EAGAIN) { + ovs_fatal(error, "pstream_accept failed"); + } + + /* Service existing connections. */ + for (i = 0; i < n_rpcs; ) { + struct jsonrpc *rpc = rpcs[i]; + struct jsonrpc_msg *msg; + + jsonrpc_run(rpc); + if (!jsonrpc_get_backlog(rpc)) { + error = jsonrpc_recv(rpc, &msg); + if (!error) { + handle_rpc(rpc, msg, &done); + jsonrpc_msg_destroy(msg); + } + } + + error = jsonrpc_get_status(rpc); + if (error) { + jsonrpc_close(rpc); + ovs_error(error, "connection closed"); + memmove(&rpcs[i], &rpcs[i + 1], + (n_rpcs - i - 1) * sizeof *rpcs); + n_rpcs--; + } else { + i++; + } + } + + /* Wait for something to do. */ + if (done && !n_rpcs) { + break; + } + pstream_wait(pstream); + for (i = 0; i < n_rpcs; i++) { + struct jsonrpc *rpc = rpcs[i]; + + jsonrpc_wait(rpc); + if (!jsonrpc_get_backlog(rpc)) { + jsonrpc_recv_wait(rpc); + } + } + poll_block(); + } +} + + +static void +do_request(int argc UNUSED, char *argv[]) +{ + struct jsonrpc_msg *msg; + struct jsonrpc *rpc; + struct json *params; + struct stream *stream; + const char *method; + char *string; + int error; + + method = argv[2]; + params = parse_json(argv[3]); + msg = jsonrpc_create_request(method, params); + string = jsonrpc_msg_is_valid(msg); + if (string) { + ovs_fatal(0, "not a valid JSON-RPC request: %s", string); + } + + error = stream_open_block(argv[1], &stream); + if (error) { + ovs_fatal(error, "could not open \"%s\"", argv[1]); + } + rpc = jsonrpc_open(stream); + + error = jsonrpc_send(rpc, msg); + if (error) { + ovs_fatal(error, "could not send request"); + } + + error = jsonrpc_recv_block(rpc, &msg); + if (error) { + ovs_fatal(error, "error waiting for reply"); + } + print_and_free_json(jsonrpc_msg_to_json(msg)); + + jsonrpc_close(rpc); +} + +static void +do_notify(int argc UNUSED, char *argv[]) +{ + struct jsonrpc_msg *msg; + struct jsonrpc *rpc; + struct json *params; + struct stream *stream; + const char *method; + char *string; + int error; + + method = argv[2]; + params = parse_json(argv[3]); + msg = jsonrpc_create_notify(method, params); + string = jsonrpc_msg_is_valid(msg); + if (string) { + ovs_fatal(0, "not a JSON RPC-valid notification: %s", string); + } + + error = stream_open_block(argv[1], &stream); + if (error) { + ovs_fatal(error, "could not open \"%s\"", argv[1]); + } + rpc = jsonrpc_open(stream); + + error = jsonrpc_send_block(rpc, msg); + if (error) { + ovs_fatal(error, "could not send request"); + } + jsonrpc_close(rpc); +} + +static void +do_help(int argc UNUSED, char *argv[] UNUSED) +{ + usage(); +} + +static struct command all_commands[] = { + { "listen", 1, 1, do_listen }, + { "request", 3, 3, do_request }, + { "notify", 3, 3, do_notify }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --git a/tests/testsuite.at b/tests/testsuite.at index 816bed1c0..781b6a6ce 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -23,6 +23,7 @@ m4_include([tests/dir_name.at]) m4_include([tests/aes128.at]) m4_include([tests/uuid.at]) m4_include([tests/json.at]) +m4_include([tests/jsonrpc.at]) m4_include([tests/timeval.at]) m4_include([tests/lockfile.at]) m4_include([tests/stp.at]) |