Commit dc470dc4 authored by Jean-Frederic Clere's avatar Jean-Frederic Clere
Browse files

First try to put togother an external health checker for mod_proxy.

parent f7c82337
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ proxy_connect_objs="mod_proxy_connect.lo"
proxy_ftp_objs="mod_proxy_ftp.lo"
proxy_http_objs="mod_proxy_http.lo"
proxy_fcgi_objs="mod_proxy_fcgi.lo"
proxy_health_checker_objs="mod_proxy_health_checker.lo health_checker_util.lo"
proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo"
proxy_balancer_objs="mod_proxy_balancer.lo"

@@ -28,6 +29,7 @@ case "$host" in
    proxy_ftp_objs="$proxy_ftp_objs mod_proxy.la"
    proxy_http_objs="$proxy_http_objs mod_proxy.la"
    proxy_fcgi_objs="$proxy_fcgi_objs mod_proxy.la"
    proxy_health_checker_objs="$proxy_health_checker_objs mod_proxy.la"
    proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
    proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la"
    ;;
@@ -37,6 +39,7 @@ APACHE_MODULE(proxy_connect, Apache proxy CONNECT module, $proxy_connect_objs, ,
APACHE_MODULE(proxy_ftp, Apache proxy FTP module, $proxy_ftp_objs, , $proxy_mods_enable)
APACHE_MODULE(proxy_http, Apache proxy HTTP module, $proxy_http_objs, , $proxy_mods_enable)
APACHE_MODULE(proxy_fcgi, Apache proxy FastCGI module, $proxy_fcgi_objs, , $proxy_mods_enable)
APACHE_MODULE(proxy_health_checker, Apache proxy health checker module, $proxy_health_checker_objs, , $proxy_mods_enable)
APACHE_MODULE(proxy_ajp, Apache proxy AJP module, $proxy_ajp_objs, , $proxy_mods_enable)
APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module, $proxy_balancer_objs, , $proxy_mods_enable)

+347 −0
Original line number Diff line number Diff line
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */


/*
 * Internal routine of the default httpd part of a health checker
 */
#define CORE_PRIVATE

#include "apr.h"
#include "apr_pools.h"

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"

#include "mod_proxy.h"
#include "slotmem.h"
#include "mod_proxy_health_checker.h"

#include "ajp.h"

static const slotmem_storage_method *checkstorage = NULL;
static ap_slotmem_t *myscore=NULL;

/* Check a AJP back-end server.
 * Send a cing message and wait for the answer
 */
static apr_status_t pingc_backend(apr_socket_t *sock, apr_pool_t *pool)
{
    ajp_msg_t *msg;
    apr_status_t rc;
    apr_byte_t result;

    rc = ajp_msg_create(pool,  &msg);
    if (rc != APR_SUCCESS)
        return rc;
    ajp_msg_serialize_cping(msg);
    rc = ajp_ilink_send(sock, msg);
    if (rc != APR_SUCCESS)
        return rc;
    ajp_msg_reuse(msg);
    rc = ajp_ilink_receive(sock, msg);
    if (rc != APR_SUCCESS)
        return rc;
    rc = ajp_msg_peek_uint8(msg, &result);
    if (rc != APR_SUCCESS)
        return rc;
    return APR_SUCCESS;
}

/*
 * Build a connection to the backend server and check it
 */
static apr_status_t test_backend(char *scheme, char *hostname, int port, apr_pool_t *pool)
{
    apr_socket_t *newsock;
    apr_sockaddr_t *epsv_addr;
    apr_status_t rv;

    if (!port) {
        if (strcmp(scheme, "ajp") == 0)
            port = 8009;
        else if (strcmp(scheme, "http") == 0)
            port = 80;
        else
            port = 443;
    }
    rv = apr_socket_create(&newsock, APR_INET, SOCK_STREAM, APR_PROTO_TCP, pool);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                    "apr_socket_create failed");
        return rv;
    }
    rv = apr_sockaddr_info_get(&epsv_addr, hostname, APR_INET, port, 0, pool);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                     "apr_sockaddr_info_get failed");
        apr_socket_close(newsock);
        return rv;
    }

    rv = apr_socket_timeout_set(newsock, 10);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
                    "apr_socket_timeout_set");
        apr_socket_close(newsock);
        return rv;
    }
    rv = apr_socket_connect(newsock, epsv_addr);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                    "apr_socket_connect failed");
        apr_socket_close(newsock);
        return rv;
    }

    /* XXX: Something is needed for http/https */
    if (strcmp(scheme, "ajp") == 0) {
        /* The connection is etablished send a ping and read the answer */
        apr_socket_timeout_set(newsock, 10000);
        rv = pingc_backend(newsock, pool);  
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                        "pingc_backend failed");
            apr_socket_close(newsock);
            return rv;
        }
    }
    apr_socket_close(newsock);
    return APR_SUCCESS;
}

/* read the size of the entry: to create the shared area */
static int getentrysize()
{
    return sizeof(struct proxy_worker_conf);
}
/* copy the worker information in the shared area so the health-checker can extract the part it need */
static apr_status_t add_entry(proxy_worker *worker, char *balancer_name, int id)
{
    struct proxy_worker_conf *workerconf = NULL;
    apr_status_t rv;

    if (myscore == NULL)
        return APR_ENOSHMAVAIL;
    rv = checkstorage->ap_slotmem_mem(myscore, worker->id, (void *) &workerconf);
    if (rv != APR_SUCCESS) {
        return rv;
    }

    if (balancer_name)
        strcpy(workerconf->balancer_name, balancer_name);
    workerconf->id = worker->id;
    workerconf->retry = worker->retry;
    workerconf->lbfactor = worker->lbfactor;
    if (worker->name)
        strcpy(workerconf->name, worker->name);
    if (worker->scheme)
        strcpy(workerconf->scheme, worker->scheme);
    if (worker->hostname)
        strcpy(workerconf->hostname, worker->hostname);
    if (worker->route)
        strcpy(workerconf->route, worker->route);
    if (worker->redirect)
        strcpy(workerconf->redirect, worker->redirect);
    workerconf->status = worker->status;
    workerconf->port = worker->port;
    workerconf->min = worker->min;
    workerconf->smax = worker->smax;
    workerconf->hmax = worker->hmax;
    workerconf->ttl = worker->ttl;
    workerconf->timeout = worker->timeout;
    workerconf->acquire = worker->acquire;
    workerconf->acquire_set = worker->acquire_set;
    workerconf->recv_buffer_size = worker->recv_buffer_size;
    workerconf->recv_buffer_size_set = worker->recv_buffer_size_set;
    workerconf->io_buffer_size = worker->io_buffer_size;
    workerconf->io_buffer_size_set = worker->io_buffer_size_set;
    workerconf->keepalive = worker->keepalive;
    workerconf->keepalive_set = worker->keepalive_set;
    workerconf->flush_packets = worker->flush_packets;
    workerconf->flush_wait = worker->flush_wait;
    workerconf->health = 0;
    workerconf->used = 1;
    return APR_SUCCESS;
}
/* Remove the entry: TO BE DONE */
static apr_status_t del_entry(int id)
{
    return APR_SUCCESS;
}
/* read the health of the entry: for httpd */
static apr_status_t get_health(int id, int *health)
{
    struct proxy_worker_conf *workerconf = NULL;
    apr_status_t rv;

    if (myscore == NULL)
        return APR_ENOSHMAVAIL;
    rv = checkstorage->ap_slotmem_mem(myscore, id, (void *) &workerconf);
    if (rv != APR_SUCCESS)
        return rv;
    *health = workerconf->health;
    return APR_SUCCESS;
}
/* set the health of the entry: for the health-checker */
static apr_status_t set_health(int id, int value)
{
    struct proxy_worker_conf *workerconf = NULL;
    apr_status_t rv;

    if (myscore == NULL)
        return APR_ENOSHMAVAIL;
    rv = checkstorage->ap_slotmem_mem(myscore, id, (void *) &workerconf);
    if (rv != APR_SUCCESS)
        return rv;
    workerconf->health = value;
    workerconf->time_checked = apr_time_now();
    return APR_SUCCESS;
}
/* read the entry stored in the shared area and build the corresponding worker structure */
static apr_status_t get_entry(int id, proxy_worker **worker, char **balancer_name, apr_pool_t *pool)
{
    struct proxy_worker_conf *workerconf = NULL;
    apr_status_t rv;

    if (myscore == NULL)
        return APR_ENOSHMAVAIL;
    rv = checkstorage->ap_slotmem_mem(myscore, id, (void *) &workerconf);
    if (rv != APR_SUCCESS)
        return rv;

    /* allocate the data */
    *worker = apr_pcalloc(pool, sizeof(proxy_worker));
    if (workerconf->balancer_name)
        *balancer_name = apr_pcalloc(pool, strlen(workerconf->balancer_name));
    else
        *balancer_name = NULL;

    /* The httpstatus is handle by httpd don't touch it here */
    (* worker)->id = workerconf->id;
    // XXX: what to do (* worker)->s = workerconf;
    (* worker)->retry = workerconf->retry;
    (* worker)->lbfactor = workerconf->lbfactor;
    if (workerconf->name)
        strcpy((* worker)->name, workerconf->name);
    if (workerconf->scheme)
        strcpy((* worker)->scheme, workerconf->scheme);
    if (workerconf->hostname)
        strcpy((* worker)->hostname, workerconf->hostname);
    if (workerconf->route)
        strcpy((* worker)->route, workerconf->route);
    if (workerconf->redirect)
        strcpy((* worker)->redirect, workerconf->redirect);
    (* worker)->status = workerconf->status;
    (* worker)->port = workerconf->port;
    (* worker)->min = workerconf->min;
    (* worker)->smax = workerconf->smax;
    (* worker)->hmax = workerconf->hmax;
    (* worker)->ttl = workerconf->ttl;
    (* worker)->timeout = workerconf->timeout;
    (* worker)->acquire = workerconf->acquire;
    (* worker)->acquire_set = workerconf->acquire_set;
    (* worker)->recv_buffer_size = workerconf->recv_buffer_size;
    (* worker)->recv_buffer_size_set = workerconf->recv_buffer_size_set;
    (* worker)->io_buffer_size = workerconf->io_buffer_size;
    (* worker)->io_buffer_size_set = workerconf->io_buffer_size_set;
    (* worker)->keepalive = workerconf->keepalive;
    (* worker)->keepalive_set = workerconf->keepalive_set;
    (* worker)->flush_packets = workerconf->flush_packets;
    (* worker)->flush_wait = workerconf->flush_wait;
    return APR_SUCCESS;
}
/* read the entry stored in the shared area */
static apr_status_t get_entryconf(int id, struct proxy_worker_conf **workerconf, char **balancer_name, apr_pool_t *pool)
{
    apr_status_t rv;

    if (myscore == NULL)
        return APR_ENOSHMAVAIL;
    rv = checkstorage->ap_slotmem_mem(myscore, id, workerconf);
    if (rv != APR_SUCCESS)
        return rv;
    *balancer_name = (*workerconf)->balancer_name;
    return APR_SUCCESS;
}

/* Test the corresponding back-end server */
static apr_status_t check_entryhealth(int id, apr_pool_t *pool) {
    apr_status_t rv;
    struct proxy_worker_conf *workerconf;

    if (myscore == NULL)
        return APR_ENOSHMAVAIL;
    rv = checkstorage->ap_slotmem_mem(myscore, id, &workerconf);
    if (rv != APR_SUCCESS)
        return rv;
    /* If the error is not initialized to the worker to be removed keep it */
    if (workerconf->used != VALID)
        return APR_SUCCESS;
    rv = test_backend(workerconf->scheme, workerconf->hostname, workerconf->port, pool);
    if (rv != APR_SUCCESS)
        workerconf->health = HEALTH_NO;
    else
        workerconf->health = HEALTH_OK;
    workerconf->time_checked = apr_time_now();
    return rv;
}

/* check the connection pool used by the worker */
static apr_status_t check_poolhealth(proxy_worker *worker, int id, apr_pool_t *pool)
{
    /* XXX: The code is missing */
    return APR_SUCCESS;
}

/* The stuff we provide */
static const health_worker_method worker_storage = {
    &getentrysize,
    &add_entry,
    &del_entry,
    &get_health,
    &set_health,
    &get_entry,
    &get_entryconf,
    &check_entryhealth
};

/* make the module usuable from outside */
health_worker_method *health_checker_get_storage()
{
    return(&worker_storage);
}

/* handle the slotmem storage */
void health_checker_init_slotmem_storage(slotmem_storage_method * storage)
{
    checkstorage = storage;
}
slotmem_storage_method * health_checker_get_slotmem_storage()
{
    return(checkstorage);
}

/* handle the slotmen itself */
void health_checker_init_slotmem(ap_slotmem_t *score)
{
     myscore = score;
}
ap_slotmem_t *health_checker_get_slotmem()
{
    return(myscore);
}
+12 −1
Original line number Diff line number Diff line
@@ -1151,11 +1151,13 @@ static const char *
        }
    }
    else {
        int adding = 0;
        proxy_worker *worker = ap_proxy_get_worker(cmd->temp_pool, conf, r);
        if (!worker) {
            const char *err = ap_proxy_add_worker(&worker, cmd->pool, conf, r);
            if (err)
                return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
            adding = 1;
        } else {
            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
                         "worker %s already used by another worker", worker->name);
@@ -1168,6 +1170,10 @@ static const char *
            if (err)
                return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
        }
       
        /* XXX: ProxyPass is not a good name look for Location? */
        if (adding)
            proxy_checkstorage_add_entry(worker, "ProxyPass");
    }
    return NULL;
}
@@ -1517,6 +1523,7 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
    const apr_array_header_t *arr;
    const apr_table_entry_t *elts;
    int i; 
    int adding = 0; 

    if (cmd->path)
        path = apr_pstrdup(cmd->pool, cmd->path);
@@ -1552,6 +1559,7 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
        const char *err;
        if ((err = ap_proxy_add_worker(&worker, cmd->pool, conf, name)) != NULL)
            return apr_pstrcat(cmd->temp_pool, "BalancerMember ", err, NULL);
        adding = 1;
    } else {
            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
                         "worker %s already used by another worker", worker->name);
@@ -1577,6 +1585,9 @@ static const char *add_member(cmd_parms *cmd, void *dummy, const char *arg)
    }
    /* Add the worker to the load balancer */
    ap_proxy_add_worker_to_balancer(cmd->pool, balancer, worker);
    /* XXX: Holy cow: The worker can belong to more that one balancer! */
    if (adding)
        proxy_checkstorage_add_entry(worker, balancer->name);
    return NULL;
}

+2 −0
Original line number Diff line number Diff line
@@ -127,6 +127,7 @@ typedef struct proxy_balancer proxy_balancer;
typedef struct proxy_worker    proxy_worker;
typedef struct proxy_conn_pool proxy_conn_pool;
typedef struct proxy_balancer_method proxy_balancer_method;
typedef struct health_worker_method health_worker_method;

typedef struct {
    apr_array_header_t *proxies;
@@ -723,6 +724,7 @@ PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
#endif

#define PROXY_LBMETHOD "proxylbmethod"
#define PROXY_CKMETHOD "proxyckmethod"

/* The number of dynamic workers that can be added when reconfiguring.
 * If this limit is reached you must stop and restart the server.
+73 −0
Original line number Diff line number Diff line
/*
 * Default httpd part of the health checker
 */
#define CORE_PRIVATE

#include "apr.h"
#include "apr_pools.h"

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"

#include "mod_proxy.h"
#include "slotmem.h"
#include "mod_proxy_health_checker.h"

static int healthck_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
                              apr_pool_t *ptemp)
{
    slotmem_storage_method *checkstorage;
    health_worker_method *worker_storage = health_checker_get_storage();
    ap_slotmem_t *myscore;
    
    checkstorage = ap_lookup_provider(SLOTMEM_STORAGE, "shared", "0");
    if (checkstorage) {
        health_checker_init_slotmem_storage(checkstorage);
    }
    if (checkstorage && worker_storage) {
        checkstorage->ap_slotmem_create(&myscore, "proxy/checker", worker_storage->getentrysize(), 128, pconf);
        health_checker_init_slotmem(myscore);
    }
    return OK;
}

/* XXX: Was to get ap_proxy_lb_workers()
static int healthck_post_config(apr_pool_t *pconf, apr_pool_t *plog,
                                apr_pool_t *ptemp, server_rec *s)
{
    slotmem_storage_method *checkstorage = health_checker_get_slotmem_storage();
    health_worker_method *worker_storage = health_checker_get_storage();
    ap_slotmem_t *myscore;

    if (checkstorage && worker_storage) {
        checkstorage->ap_slotmem_create(&myscore, "proxy/checker", worker_storage->getentrysize(), ap_proxy_lb_workers(), pconf);
        health_checker_init_slotmem(myscore);
    }
    return OK;

}
 */

static void ap_healthstore_register_hook(apr_pool_t *p)
{
    static const char * const aszPre[] = { "mod_proxy.c", NULL };
    static const char * const aszPos[] = { "mod_sharedmem.c", NULL };

    health_worker_method *worker_storage = health_checker_get_storage();
    ap_register_provider(p, PROXY_CKMETHOD, "default", "0", worker_storage);
    ap_hook_pre_config(healthck_pre_config, NULL, aszPos, APR_HOOK_MIDDLE);
    /* XXX: Too late....
    ap_hook_post_config(healthck_post_config, aszPre, NULL, APR_HOOK_MIDDLE);
     */
}

module AP_MODULE_DECLARE_DATA proxy_health_checker_module = {
    STANDARD20_MODULE_STUFF,
    NULL,       /* create per-directory config structure */
    NULL,       /* merge per-directory config structures */
    NULL,       /* create per-server config structure */
    NULL,       /* merge per-server config structures */
    NULL,       /* command apr_table_t */
    ap_healthstore_register_hook /* register hooks */
};
Loading