root/third_party/libevent/test/regress_rpc.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. http_setup
  2. MessageCb
  3. EVRPC_STRUCT
  4. rpc_setup
  5. rpc_teardown
  6. rpc_postrequest_failure
  7. rpc_basic_test
  8. rpc_postrequest_done
  9. rpc_basic_message
  10. rpc_pool_with_connection
  11. GotKillCb
  12. GotKillCbTwo
  13. rpc_hook_add_header
  14. rpc_hook_remove_header
  15. rpc_basic_client
  16. rpc_basic_queued_client
  17. GotErrorCb
  18. rpc_client_timeout
  19. rpc_suite

/*
 * Copyright (c) 2003-2006 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/queue.h>
#ifndef WIN32
#include <sys/socket.h>
#include <signal.h>
#include <unistd.h>
#include <netdb.h>
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include "event.h"
#include "evhttp.h"
#include "log.h"
#include "evrpc.h"

#include "regress.gen.h"

void rpc_suite(void);

extern int test_ok;

static struct evhttp *
http_setup(short *pport)
{
        int i;
        struct evhttp *myhttp;
        short port = -1;

        /* Try a few different ports */
        for (i = 0; i < 50; ++i) {
                myhttp = evhttp_start("127.0.0.1", 8080 + i);
                if (myhttp != NULL) {
                        port = 8080 + i;
                        break;
                }
        }

        if (port == -1)
                event_errx(1, "Could not start web server");

        *pport = port;
        return (myhttp);
}

EVRPC_HEADER(Message, msg, kill);
EVRPC_HEADER(NeverReply, msg, kill);

EVRPC_GENERATE(Message, msg, kill);
EVRPC_GENERATE(NeverReply, msg, kill);

static int need_input_hook = 0;
static int need_output_hook = 0;

static void
MessageCb(EVRPC_STRUCT(Message)* rpc, void *arg)
{
        struct kill* kill_reply = rpc->reply;

        if (need_input_hook) {
                struct evhttp_request* req = EVRPC_REQUEST_HTTP(rpc);
                const char *header = evhttp_find_header(
                        req->input_headers, "X-Hook");
                assert(strcmp(header, "input") == 0);
        }

        /* we just want to fill in some non-sense */
        EVTAG_ASSIGN(kill_reply, weapon, "dagger");
        EVTAG_ASSIGN(kill_reply, action, "wave around like an idiot");

        /* no reply to the RPC */
        EVRPC_REQUEST_DONE(rpc);
}

static EVRPC_STRUCT(NeverReply) *saved_rpc;

static void
NeverReplyCb(EVRPC_STRUCT(NeverReply)* rpc, void *arg)
{
        test_ok += 1;
        saved_rpc = rpc;
}

static void
rpc_setup(struct evhttp **phttp, short *pport, struct evrpc_base **pbase)
{
        short port;
        struct evhttp *http = NULL;
        struct evrpc_base *base = NULL;

        http = http_setup(&port);
        base = evrpc_init(http);
        
        EVRPC_REGISTER(base, Message, msg, kill, MessageCb, NULL);
        EVRPC_REGISTER(base, NeverReply, msg, kill, NeverReplyCb, NULL);

        *phttp = http;
        *pport = port;
        *pbase = base;

        need_input_hook = 0;
        need_output_hook = 0;
}

static void
rpc_teardown(struct evrpc_base *base)
{
        assert(EVRPC_UNREGISTER(base, Message) == 0);
        assert(EVRPC_UNREGISTER(base, NeverReply) == 0);

        evrpc_free(base);
}

static void
rpc_postrequest_failure(struct evhttp_request *req, void *arg)
{
        if (req->response_code != HTTP_SERVUNAVAIL) {
        
                fprintf(stderr, "FAILED (response code)\n");
                exit(1);
        }

        test_ok = 1;
        event_loopexit(NULL);
}

/*
 * Test a malformed payload submitted as an RPC
 */

static void
rpc_basic_test(void)
{
        short port;
        struct evhttp *http = NULL;
        struct evrpc_base *base = NULL;
        struct evhttp_connection *evcon = NULL;
        struct evhttp_request *req = NULL;

        fprintf(stdout, "Testing Basic RPC Support: ");

        rpc_setup(&http, &port, &base);

        evcon = evhttp_connection_new("127.0.0.1", port);
        if (evcon == NULL) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        /*
         * At this point, we want to schedule an HTTP POST request
         * server using our make request method.
         */

        req = evhttp_request_new(rpc_postrequest_failure, NULL);
        if (req == NULL) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        /* Add the information that we care about */
        evhttp_add_header(req->output_headers, "Host", "somehost");
        evbuffer_add_printf(req->output_buffer, "Some Nonsense");
        
        if (evhttp_make_request(evcon, req,
                EVHTTP_REQ_POST,
                "/.rpc.Message") == -1) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        test_ok = 0;

        event_dispatch();

        evhttp_connection_free(evcon);

        rpc_teardown(base);
        
        if (test_ok != 1) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        fprintf(stdout, "OK\n");

        evhttp_free(http);
}

static void
rpc_postrequest_done(struct evhttp_request *req, void *arg)
{
        struct kill* kill_reply = NULL;

        if (req->response_code != HTTP_OK) {
        
                fprintf(stderr, "FAILED (response code)\n");
                exit(1);
        }

        kill_reply = kill_new();

        if ((kill_unmarshal(kill_reply, req->input_buffer)) == -1) {
                fprintf(stderr, "FAILED (unmarshal)\n");
                exit(1);
        }
        
        kill_free(kill_reply);

        test_ok = 1;
        event_loopexit(NULL);
}

static void
rpc_basic_message(void)
{
        short port;
        struct evhttp *http = NULL;
        struct evrpc_base *base = NULL;
        struct evhttp_connection *evcon = NULL;
        struct evhttp_request *req = NULL;
        struct msg *msg;

        fprintf(stdout, "Testing Good RPC Post: ");

        rpc_setup(&http, &port, &base);

        evcon = evhttp_connection_new("127.0.0.1", port);
        if (evcon == NULL) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        /*
         * At this point, we want to schedule an HTTP POST request
         * server using our make request method.
         */

        req = evhttp_request_new(rpc_postrequest_done, NULL);
        if (req == NULL) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        /* Add the information that we care about */
        evhttp_add_header(req->output_headers, "Host", "somehost");

        /* set up the basic message */
        msg = msg_new();
        EVTAG_ASSIGN(msg, from_name, "niels");
        EVTAG_ASSIGN(msg, to_name, "tester");
        msg_marshal(req->output_buffer, msg);
        msg_free(msg);

        if (evhttp_make_request(evcon, req,
                EVHTTP_REQ_POST,
                "/.rpc.Message") == -1) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        test_ok = 0;

        event_dispatch();

        evhttp_connection_free(evcon);
        
        rpc_teardown(base);
        
        if (test_ok != 1) {
                fprintf(stdout, "FAILED\n");
                exit(1);
        }

        fprintf(stdout, "OK\n");

        evhttp_free(http);
}

static struct evrpc_pool *
rpc_pool_with_connection(short port)
{
        struct evhttp_connection *evcon;
        struct evrpc_pool *pool;

        pool = evrpc_pool_new(NULL);
        assert(pool != NULL);

        evcon = evhttp_connection_new("127.0.0.1", port);
        assert(evcon != NULL);

        evrpc_pool_add_connection(pool, evcon);
        
        return (pool);
}

static void
GotKillCb(struct evrpc_status *status,
    struct msg *msg, struct kill *kill, void *arg)
{
        char *weapon;
        char *action;

        if (need_output_hook) {
                struct evhttp_request *req = status->http_req;
                const char *header = evhttp_find_header(
                        req->input_headers, "X-Pool-Hook");
                assert(strcmp(header, "ran") == 0);
        }

        if (status->error != EVRPC_STATUS_ERR_NONE)
                goto done;

        if (EVTAG_GET(kill, weapon, &weapon) == -1) {
                fprintf(stderr, "get weapon\n");
                goto done;
        }
        if (EVTAG_GET(kill, action, &action) == -1) {
                fprintf(stderr, "get action\n");
                goto done;
        }

        if (strcmp(weapon, "dagger"))
                goto done;

        if (strcmp(action, "wave around like an idiot"))
                goto done;

        test_ok += 1;

done:
        event_loopexit(NULL);
}

static void
GotKillCbTwo(struct evrpc_status *status,
    struct msg *msg, struct kill *kill, void *arg)
{
        char *weapon;
        char *action;

        if (status->error != EVRPC_STATUS_ERR_NONE)
                goto done;

        if (EVTAG_GET(kill, weapon, &weapon) == -1) {
                fprintf(stderr, "get weapon\n");
                goto done;
        }
        if (EVTAG_GET(kill, action, &action) == -1) {
                fprintf(stderr, "get action\n");
                goto done;
        }

        if (strcmp(weapon, "dagger"))
                goto done;

        if (strcmp(action, "wave around like an idiot"))
                goto done;

        test_ok += 1;

done:
        if (test_ok == 2)
                event_loopexit(NULL);
}

static int
rpc_hook_add_header(struct evhttp_request *req,
    struct evbuffer *evbuf, void *arg)
{
        const char *hook_type = arg;
        if (strcmp("input", hook_type) == 0)
                evhttp_add_header(req->input_headers, "X-Hook", hook_type);
        else 
                evhttp_add_header(req->output_headers, "X-Hook", hook_type);
        return (0);
}

static int
rpc_hook_remove_header(struct evhttp_request *req,
    struct evbuffer *evbuf, void *arg)
{
        const char *header = evhttp_find_header(req->input_headers, "X-Hook");
        assert(header != NULL);
        assert(strcmp(header, arg) == 0);
        evhttp_remove_header(req->input_headers, "X-Hook");
        evhttp_add_header(req->input_headers, "X-Pool-Hook", "ran");

        return (0);
}

static void
rpc_basic_client(void)
{
        short port;
        struct evhttp *http = NULL;
        struct evrpc_base *base = NULL;
        struct evrpc_pool *pool = NULL;
        struct msg *msg;
        struct kill *kill;

        fprintf(stdout, "Testing RPC Client: ");

        rpc_setup(&http, &port, &base);

        need_input_hook = 1;
        need_output_hook = 1;

        assert(evrpc_add_hook(base, EVRPC_INPUT, rpc_hook_add_header, (void*)"input")
            != NULL);
        assert(evrpc_add_hook(base, EVRPC_OUTPUT, rpc_hook_add_header, (void*)"output")
            != NULL);

        pool = rpc_pool_with_connection(port);

        assert(evrpc_add_hook(pool, EVRPC_INPUT, rpc_hook_remove_header, (void*)"output"));

        /* set up the basic message */
        msg = msg_new();
        EVTAG_ASSIGN(msg, from_name, "niels");
        EVTAG_ASSIGN(msg, to_name, "tester");

        kill = kill_new();

        EVRPC_MAKE_REQUEST(Message, pool, msg, kill,  GotKillCb, NULL);

        test_ok = 0;

        event_dispatch();
        
        if (test_ok != 1) {
                fprintf(stdout, "FAILED (1)\n");
                exit(1);
        }

        /* we do it twice to make sure that reuse works correctly */
        kill_clear(kill);

        EVRPC_MAKE_REQUEST(Message, pool, msg, kill,  GotKillCb, NULL);

        event_dispatch();
        
        rpc_teardown(base);
        
        if (test_ok != 2) {
                fprintf(stdout, "FAILED (2)\n");
                exit(1);
        }

        fprintf(stdout, "OK\n");

        msg_free(msg);
        kill_free(kill);

        evrpc_pool_free(pool);
        evhttp_free(http);
}

/* 
 * We are testing that the second requests gets send over the same
 * connection after the first RPCs completes.
 */
static void
rpc_basic_queued_client(void)
{
        short port;
        struct evhttp *http = NULL;
        struct evrpc_base *base = NULL;
        struct evrpc_pool *pool = NULL;
        struct msg *msg;
        struct kill *kill_one, *kill_two;

        fprintf(stdout, "Testing RPC (Queued) Client: ");

        rpc_setup(&http, &port, &base);

        pool = rpc_pool_with_connection(port);

        /* set up the basic message */
        msg = msg_new();
        EVTAG_ASSIGN(msg, from_name, "niels");
        EVTAG_ASSIGN(msg, to_name, "tester");

        kill_one = kill_new();
        kill_two = kill_new();

        EVRPC_MAKE_REQUEST(Message, pool, msg, kill_one,  GotKillCbTwo, NULL);
        EVRPC_MAKE_REQUEST(Message, pool, msg, kill_two,  GotKillCb, NULL);

        test_ok = 0;

        event_dispatch();
        
        rpc_teardown(base);
        
        if (test_ok != 2) {
                fprintf(stdout, "FAILED (1)\n");
                exit(1);
        }

        fprintf(stdout, "OK\n");

        msg_free(msg);
        kill_free(kill_one);
        kill_free(kill_two);

        evrpc_pool_free(pool);
        evhttp_free(http);
}

static void
GotErrorCb(struct evrpc_status *status,
    struct msg *msg, struct kill *kill, void *arg)
{
        if (status->error != EVRPC_STATUS_ERR_TIMEOUT)
                goto done;

        /* should never be complete but just to check */
        if (kill_complete(kill) == 0)
                goto done;

        test_ok += 1;

done:
        event_loopexit(NULL);
}

static void
rpc_client_timeout(void)
{
        short port;
        struct evhttp *http = NULL;
        struct evrpc_base *base = NULL;
        struct evrpc_pool *pool = NULL;
        struct msg *msg;
        struct kill *kill;

        fprintf(stdout, "Testing RPC Client Timeout: ");

        rpc_setup(&http, &port, &base);

        pool = rpc_pool_with_connection(port);

        /* set the timeout to 5 seconds */
        evrpc_pool_set_timeout(pool, 5);

        /* set up the basic message */
        msg = msg_new();
        EVTAG_ASSIGN(msg, from_name, "niels");
        EVTAG_ASSIGN(msg, to_name, "tester");

        kill = kill_new();

        EVRPC_MAKE_REQUEST(NeverReply, pool, msg, kill, GotErrorCb, NULL);

        test_ok = 0;

        event_dispatch();
        
        /* free the saved RPC structure up */
        EVRPC_REQUEST_DONE(saved_rpc);

        rpc_teardown(base);
        
        if (test_ok != 2) {
                fprintf(stdout, "FAILED (1)\n");
                exit(1);
        }

        fprintf(stdout, "OK\n");

        msg_free(msg);
        kill_free(kill);

        evrpc_pool_free(pool);
        evhttp_free(http);
}

void
rpc_suite(void)
{
        rpc_basic_test();
        rpc_basic_message();
        rpc_basic_client();
        rpc_basic_queued_client();
        rpc_client_timeout();
}

/* [<][>][^][v][top][bottom][index][help] */