From 33aceece57896b97e93d4b67347ec340c7c0fa85 Mon Sep 17 00:00:00 2001 From: Jim Jagielski Date: Tue, 29 Dec 2015 16:12:04 +0000 Subject: [PATCH] Commit framework impl of health-check module plus required changes. The actual health checking is currently in progress, but wanted to add in at this stage. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1722177 13f79535-47bb-0310-9956-ffa450edef68 --- modules/proxy/config.m4 | 5 + modules/proxy/mod_proxy.c | 17 +- modules/proxy/mod_proxy.h | 9 + modules/proxy/mod_proxy_hcheck.c | 279 +++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 modules/proxy/mod_proxy_hcheck.c diff --git a/modules/proxy/config.m4 b/modules/proxy/config.m4 index df5d4087d6..0ca0b49a58 100644 --- a/modules/proxy/config.m4 +++ b/modules/proxy/config.m4 @@ -10,6 +10,10 @@ else proxy_mods_enable=most fi +if test "$proxy_mods_enable" = "no"; then + enable_proxy_hcheck=no +fi + proxy_objs="mod_proxy.lo proxy_util.lo" APACHE_MODULE(proxy, Apache proxy module, $proxy_objs, , $proxy_mods_enable) @@ -68,6 +72,7 @@ APACHE_MODULE(serf, [Reverse proxy module using Serf], , , no, [ ]) APACHE_MODULE(proxy_express, mass reverse-proxy module. Requires --enable-proxy., , , $proxy_mods_enable,, proxy) +APACHE_MODULE(proxy_hcheck, reverse-proxy health-check module. Requires --enable-proxy and --enable-watchdog., , , $enable_proxy_hcheck,, watchdog) APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 3509cbdf7a..b7f628f7d3 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -36,6 +36,13 @@ APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, #define MAX(x,y) ((x) >= (y) ? (x) : (y)) #endif +/* + * We do health-checks only if that (sub)module is loaded in. This + * allows for us to continue as is w/o requiring mod_watchdog for + * those implementations which aren't using health checks + */ +static APR_OPTIONAL_FN_TYPE(set_worker_hc_param) *set_worker_hc_param_f = NULL; + static const char * const proxy_id = "proxy"; apr_global_mutex_t *proxy_mutex = NULL; @@ -274,7 +281,11 @@ static const char *set_worker_param(apr_pool_t *p, PROXY_STRNCPY(worker->s->flusher, val); } else { - return "unknown Worker parameter"; + if (set_worker_hc_param_f) { + return set_worker_hc_param_f(p, worker, key, val, NULL); + } else { + return "unknown Worker parameter"; + } } return NULL; } @@ -2667,6 +2678,7 @@ static int proxy_post_config(apr_pool_t *pconf, apr_pool_t *plog, proxy_ssl_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable); proxy_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); proxy_ssl_val = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); + set_worker_hc_param_f = APR_RETRIEVE_OPTIONAL_FN(set_worker_hc_param); ap_proxy_strmatch_path = apr_strmatch_precompile(pconf, "path=", 0); ap_proxy_strmatch_domain = apr_strmatch_precompile(pconf, "domain=", 0); @@ -2889,7 +2901,8 @@ static void register_hooks(apr_pool_t *p) * make sure that we are called after the mpm * initializes. */ - static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy_balancer.c", NULL}; + static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy_balancer.c", + "mod_proxy_hcheck.c", NULL}; /* handler */ ap_hook_handler(proxy_handler, NULL, NULL, APR_HOOK_FIRST); diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 7361ab20c4..c3d12186c4 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -359,6 +359,7 @@ typedef struct { char redirect[PROXY_WORKER_MAX_ROUTE_SIZE]; /* temporary balancing redirection route */ char flusher[PROXY_WORKER_MAX_SCHEME_SIZE]; /* flush provider used by mod_proxy_fdpass */ char uds_path[PROXY_WORKER_MAX_NAME_SIZE]; /* path to worker's unix domain socket if applicable */ + char hurl[PROXY_WORKER_MAX_ROUTE_SIZE]; /* health check url */ int lbset; /* load balancer cluster set */ int retries; /* number of retries on this worker */ int lbstatus; /* Current lbstatus */ @@ -368,6 +369,9 @@ typedef struct { int hmax; /* Hard maximum on the total number of connections */ int flush_wait; /* poll wait time in microseconds if flush_auto */ int index; /* shm array index */ + int method; /* method to use for health check */ + int passes; /* number of successes for check to pass */ + int fails; /* number of failures for check to fail */ proxy_hashes hash; /* hash of worker name */ unsigned int status; /* worker status bitfield */ enum { @@ -384,6 +388,7 @@ typedef struct { apr_interval_time_t acquire; /* acquire timeout when the maximum number of connections is exceeded */ apr_interval_time_t ping_timeout; apr_interval_time_t conn_timeout; + apr_interval_time_t interval; apr_size_t recv_buffer_size; apr_size_t io_buffer_size; apr_size_t elected; /* Number of times the worker was elected */ @@ -519,6 +524,10 @@ struct proxy_balancer_method { #define PROXY_DECLARE_DATA __declspec(dllimport) #endif +APR_DECLARE_OPTIONAL_FN(const char *, set_worker_hc_param, + (apr_pool_t *, proxy_worker *, + const char *, const char *, void *)); + APR_DECLARE_EXTERNAL_HOOK(proxy, PROXY, int, scheme_handler, (request_rec *r, proxy_worker *worker, proxy_server_conf *conf, char *url, const char *proxyhost, apr_port_t proxyport)) diff --git a/modules/proxy/mod_proxy_hcheck.c b/modules/proxy/mod_proxy_hcheck.c new file mode 100644 index 0000000000..a2ac2311fa --- /dev/null +++ b/modules/proxy/mod_proxy_hcheck.c @@ -0,0 +1,279 @@ +/* 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. + */ + +#include "mod_proxy.h" +#include "mod_watchdog.h" + +module AP_MODULE_DECLARE_DATA proxy_hcheck_module; + +#define HCHECK_WATHCHDOG_NAME ("_proxy_hcheck_") +/* default to health check every 30 seconds */ +#define HCHECK_WATHCHDOG_SEC (30) +/* The watchdog runs every 5 seconds, which is also the minimal check */ +#define HCHECK_WATHCHDOG_INTERVAL (5) + +static char *methods[] = { + "NULL", "OPTIONS", "HEAD", "GET", "POST", "CPING" +}; + +typedef struct hcheck_template_t { + char *name; + int method; + int passes; + int fails; + apr_interval_time_t interval; + char *hurl; +} hcheck_template_t; + +static apr_pool_t *ptemplate = NULL; +static apr_array_header_t *templates = NULL; +static ap_watchdog_t *watchdog; + +/* + * This is not as clean as it should be, because we are using + * the same to both update the actual worker as well as verifying + * and populating the health check 'template' as well. + */ +static const char *set_worker_hc_param(apr_pool_t *p, + proxy_worker *worker, + const char *key, + const char *val, + void *tmp) +{ + int ival; + hcheck_template_t *ctx; + + if (!worker && !tmp) { + return "Bad call to set_worker_hc_param()"; + } + ctx = (hcheck_template_t *)tmp; + if (!strcasecmp(key, "hcheck")) { + hcheck_template_t *template; + template = (hcheck_template_t *)templates->elts; + for (ival = 0; ival < templates->nelts; ival++, template++) { + if (!ap_casecmpstr(template->name, val)) { + worker->s->method = template->method; + worker->s->interval = template->interval; + worker->s->passes = template->passes; + worker->s->fails = template->fails; + PROXY_STRNCPY(worker->s->hurl, template->hurl); + return NULL; + } + } + return apr_psprintf(p, "Unknown HCheckTemplate name: %s", val); + } + else if (!strcasecmp(key, "method")) { + for (ival = 1; ival < sizeof(methods); ival++) { + if (!ap_casecmpstr(val, methods[ival])) { + if (worker) { + worker->s->method = ival; + } else { + ctx->method = ival; + } + return NULL; + } + } + return "Unknown method"; + } + else if (!strcasecmp(key, "interval")) { + ival = atoi(val); + if (ival < 5) + return "Interval must be a positive value greater than 5 seconds"; + if (worker) { + worker->s->interval = apr_time_from_sec(ival); + } else { + ctx->interval = apr_time_from_sec(ival); + } + } + else if (!strcasecmp(key, "passes")) { + ival = atoi(val); + if (ival < 0) + return "Passes must be a positive value"; + if (worker) { + worker->s->passes = ival; + } else { + ctx->passes = ival; + } + } + else if (!strcasecmp(key, "fails")) { + ival = atoi(val); + if (ival < 0) + return "Fails must be a positive value"; + if (worker) { + worker->s->fails = ival; + } else { + ctx->fails = ival; + } + } + else if (!strcasecmp(key, "hurl")) { + if (strlen(val) >= sizeof(worker->s->hurl)) + return apr_psprintf(p, "Health check hurl length must be < %d characters", + (int)sizeof(worker->s->hurl)); + if (worker) { + PROXY_STRNCPY(worker->s->hurl, val); + } else { + ctx->hurl = apr_pstrdup(p, val); + } + } + else { + return "unknown Worker hcheck parameter"; + } + return NULL; +} + +static const char *set_hcheck(cmd_parms *cmd, void *dummy, const char *arg) +{ + char *name = NULL; + char *word, *val; + hcheck_template_t template; + hcheck_template_t *tpush; + const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS); + if (err) + return err; + + template.name = ap_getword_conf(cmd->temp_pool, &arg); + template.method = template.passes = template.fails = 1; + template.interval = apr_time_from_sec(HCHECK_WATHCHDOG_SEC); + template.hurl = NULL; + while (*arg) { + word = ap_getword_conf(cmd->pool, &arg); + val = strchr(word, '='); + if (!val) { + return "Invalid HCheckTemplate parameter. Parameter must be " + "in the form 'key=value'"; + } + else + *val++ = '\0'; + err = set_worker_hc_param(cmd->pool, NULL, word, val, &template); + + if (err) + return apr_pstrcat(cmd->temp_pool, "HCheckTemplate: ", err, " ", word, "=", val, "; ", name, NULL); + /* No error means we have a valid template */ + tpush = (hcheck_template_t *)apr_array_push(templates); + memcpy(tpush, &template, sizeof(hcheck_template_t)); + } + + return NULL; +} + +static apr_status_t hc_watchdog_callback(int state, void *data, + apr_pool_t *pool) +{ + apr_status_t rv = APR_SUCCESS; + apr_time_t cur, now; + + + switch (state) { + case AP_WATCHDOG_STATE_STARTING: + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO() + "%s watchdog started.", + HCHECK_WATHCHDOG_NAME); + break; + case AP_WATCHDOG_STATE_RUNNING: + cur = now = apr_time_sec(apr_time_now()); + /* + while ((now - cur) < apr_time_sec(ctx->interval)) { + break; + } + */ + break; + case AP_WATCHDOG_STATE_STOPPING: + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO() + "stopping %s watchdog.", + HCHECK_WATHCHDOG_NAME); + + break; + } + return rv; +} + +static int hc_pre_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + if (!ptemplate) { + apr_pool_create(&ptemplate, p); + } + if (!templates) { + templates = apr_array_make(ptemplate, 10, sizeof(hcheck_template_t)); + } + return OK; +} + +static int hc_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + apr_status_t rv; + APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *hc_watchdog_get_instance; + APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *hc_watchdog_register_callback; + + hc_watchdog_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance); + hc_watchdog_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback); + if (!hc_watchdog_get_instance || !hc_watchdog_register_callback) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO() + "mod_watchdog is required"); + return !OK; + } + + rv = hc_watchdog_get_instance(&watchdog, + HCHECK_WATHCHDOG_NAME, + 0, 1, p); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO() + "Failed to create watchdog instance (%s)", + HCHECK_WATHCHDOG_NAME); + return !OK; + } + rv = hc_watchdog_register_callback(watchdog, + apr_time_from_sec(HCHECK_WATHCHDOG_INTERVAL), + NULL, + hc_watchdog_callback); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO() + "Failed to register watchdog callback (%s)", + HCHECK_WATHCHDOG_NAME); + return !OK; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO() + "watchdog callback registered (%s)", HCHECK_WATHCHDOG_NAME); + return OK; +} + +static const command_rec command_table[] = { + AP_INIT_RAW_ARGS("HCheckTemplate", set_hcheck, NULL, OR_FILEINFO, + "Health check template"), + { NULL } +}; + +static void hc_register_hooks(apr_pool_t *p) +{ + static const char *const runAfter[] = { "mod_watchdog.c", NULL}; + APR_REGISTER_OPTIONAL_FN(set_worker_hc_param); + ap_hook_pre_config(hc_pre_config, NULL, NULL, APR_HOOK_LAST); + ap_hook_post_config(hc_post_config, NULL, runAfter, APR_HOOK_LAST); +} + +/* the main config structure */ + +AP_DECLARE_MODULE(proxy_hcheck) = +{ + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + NULL, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + command_table, /* table of config file commands */ + hc_register_hooks /* register hooks */ +};