mod_proxy_balancer.c revision 6dc105fdb660c38bd9adc04c39a3b58c5be14b29
/* 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
*
*
* 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.
*/
/* Load balancer module for Apache proxy */
#include "mod_proxy.h"
#include "scoreboard.h"
#include "ap_mpm.h"
#include "apr_version.h"
#include "ap_hooks.h"
#include "apr_date.h"
static const char *balancer_mutex_type = "proxy-balancer-shm";
static int (*ap_proxy_retry_worker_fn)(const char *proxy_function,
/*
* Register our mutex type before the config is read so we
* can adjust the mutex settings using the Mutex directive.
*/
{
APR_LOCK_DEFAULT, 0);
if (rv != APR_SUCCESS) {
return rv;
}
return OK;
}
#if 0
#endif
{
const char *err;
apr_port_t port = 0;
/* TODO: offset of BALANCER_PREFIX ?? */
url += 9;
}
else {
return DECLINED;
}
/* do syntatic check.
* We break the URL into host, port, path, search
*/
if (err) {
"error parsing URL %s: %s",
return HTTP_BAD_REQUEST;
}
/*
* process the path. With proxy-noncanon set (by
* mod_proxy) we use the raw, unparsed uri
*/
}
else {
r->proxyreq);
}
return HTTP_BAD_REQUEST;
return OK;
}
{
int i;
if (!worker_is_initialized) {
ap_proxy_initialize_worker(worker, s, p);
}
++workers;
}
/* Set default number of attempts to the number of
* workers.
*/
}
}
/* Retrieve the parameter with the given name
* Something like 'JSESSIONID=12345...N'
*/
const char *name, int scolon_sep)
{
char *pathdelims = "?&";
if (scolon_sep) {
pathdelims = ";?&";
}
if (*path == '=') {
/*
* Session path was found, get its value
*/
++path;
if (*path) {
char *q;
return path;
}
}
}
return NULL;
}
{
const char *cookies;
const char *start_cookie;
if (start_cookie == cookies ||
++start_cookie;
/*
* Session cookie was found, get its value
*/
char *end_cookie, *cookie;
*end_cookie = '\0';
*end_cookie = '\0';
return cookie;
}
}
}
}
return NULL;
}
/* Find the worker that has the 'route' defined
*/
const char *route, request_rec *r)
{
int i;
int checking_standby;
int checked_standby;
checking_standby = checked_standby = 0;
while (!checked_standby) {
continue;
if (PROXY_WORKER_IS_USABLE(worker)) {
return worker;
} else {
/*
* If the worker is in error state run
* retry on that worker. It will be marked as
* operational if the retry timeout is elapsed.
* The worker might still be unusable, but we try
* anyway.
*/
if (PROXY_WORKER_IS_USABLE(worker)) {
return worker;
} else {
/*
* We have a worker that is unusable.
* It can be in error or disabled, but in case
* it has a redirection set use that redirection worker.
* This enables to safely remove the member from the
* balancer. Of course you will need some kind of
* session replication between those two remote.
*/
/* Check if the redirect worker is usable */
/*
* If the worker is in error state run
* retry on that worker. It will be marked as
* operational if the retry timeout is elapsed.
* The worker might still be unusable, but we try
* anyway.
*/
}
return rworker;
}
}
}
}
}
}
return NULL;
}
request_rec *r,
char **route,
const char **sticky_used,
char **url)
{
return NULL;
/* Try to find the sticky route inside url */
if (*route) {
"Found value %s for stickysession %s",
}
else {
if (*route) {
"Found value %s for stickysession %s",
}
}
/*
* If we found a value for sticksession, find the first '.' within.
* Everything after '.' (if present) is our route.
*/
(*route)++;
/* We have a route in path or in cookie
* Find the worker that has this route defined.
*/
/*
* Notice that the route of the worker chosen is different from
* the route supplied by the client.
*/
"Route changed from %s to %s",
}
return worker;
}
else
return NULL;
}
request_rec *r)
{
"%s: Lock failed for find_best_worker()",
return NULL;
}
if (candidate)
"%s: Unlock failed for find_best_worker()",
}
/* All the workers are in error state or disabled.
* If the balancer has a timeout sleep for a while
* and try again to find the worker. The chances are
* that some other thread will release a connection.
* By default the timeout is not set, and the server
* returns SERVER_BUSY.
*/
/* XXX: This can perhaps be build using some
* smarter mechanism, like tread_cond.
* But since the statuses can came from
* different childs, use the provided algo.
*/
/* Set the timeout to 0 so that we don't
* end in infinite loop
*/
/* Try again */
break;
}
/* restore the timeout */
}
}
return candidate;
}
char **url)
{
if (scheme)
/* we break the URL into host, port, uri */
if (!worker) {
"missing worker. URI cannot be parsed: ", *url,
NULL));
}
return OK;
}
{
int i;
int ok = 0;
ok = 1;
break;
}
else {
/* Try if we can recover */
ok = 1;
break;
}
}
}
/* If all workers are in error state force the recovery.
*/
"%s: Forcing recovery for worker (%s)",
}
}
}
{
}
return APR_SUCCESS;
}
request_rec *r,
{
int access_status;
/* Step 1: check if the url is for us
* The url we can handle starts with 'balancer://'
* If balancer is already provided skip the search
* for balancer, because this is failover attempt.
*/
if (!*balancer &&
return DECLINED;
/* Step 2: Lock the LoadBalancer
* XXX: perhaps we need the process lock here
*/
return DECLINED;
}
/* Step 3: force recovery */
/* Step 3.5: Update member list for the balancer */
/* TODO: Implement as provider! */
/* Step 4: find the session route */
if (runtime) {
/* Call the LB implementation */
}
else { /* Use the default one */
int i, total_factor = 0;
/* We have a sticky load balancer
* Update the workers status
* so that even session routes get
* into account.
*/
/* Take into calculation only the workers that are
* not in error state or not disabled.
*/
if (PROXY_WORKER_IS_USABLE(*workers)) {
}
workers++;
}
}
}
int i, member_of = 0;
/*
* We have a route provided that doesn't match the
* balancer name. See if the provider route is the
* member of the same balancer in which case return 503
*/
member_of = 1;
break;
}
workers++;
}
if (member_of) {
"%s: All workers are in error state for route (%s)",
"%s: Unlock failed for pre_request",
}
return HTTP_SERVICE_UNAVAILABLE;
}
}
"%s: Unlock failed for pre_request",
}
if (!*worker) {
if (!runtime) {
"%s: All workers are in error state",
} else {
"%s: No workers in balancer",
}
return HTTP_SERVICE_UNAVAILABLE;
}
/*
* This balancer has sticky sessions and the client either has not
* supplied any routing information or all workers for this route
* including possible redirect and hotstandby workers are in error
* state, but we have found another working worker for this
* balancer where we can send the request. Thus notice that we have
* changed the route to the backend.
*/
}
}
/* Rewrite the url from 'balancer://url'
* to the 'worker_scheme://worker_hostname[:worker_port]/url'
* This replaces the balancers fictional name with the
* real hostname of the elected worker.
*/
/* Add the session route to request notes if present */
if (route) {
/* Add session info to env. */
"BALANCER_SESSION_STICKY", sticky);
"BALANCER_SESSION_ROUTE", route);
}
"%s: worker (%s) rewritten to %s",
return access_status;
}
request_rec *r,
{
"%s: Lock failed for post_request",
return HTTP_INTERNAL_SERVER_ERROR;
}
int i;
"%s: Forcing worker (%s) into error state "
"due to status code %d matching 'failonstatus' "
"balancer parameter",
break;
}
}
}
}
return OK;
}
{
int i;
/* Recalculate lbfactors */
/* Special case if there is only one worker its
* load factor will always be 1
*/
return;
}
/* Update the status entries */
}
}
{
int i;
server_rec *s = data;
void *sconf = s->module_config;
}
}
return(0);
}
/* post_config hook: */
{
void *sconf = s->module_config;
/* balancer_post_config() will be called twice during startup. So, don't
* set up the static data the 1st time through. */
return OK;
}
if (!ap_proxy_retry_worker_fn) {
if (!ap_proxy_retry_worker_fn) {
"mod_proxy must be loaded for mod_proxy_balancer");
return !OK;
}
}
/*
* Get slotmem setups
*/
if (!storage) {
"Failed to lookup provider 'shm' for '%s': is "
"mod_slotmem_shm loaded??",
return !OK;
}
tstamp = apr_time_now();
/*
* Go thru each Vhost and create the shared mem slotmem for
* each balancer's workers
*/
while (s) {
int i,j;
char *id;
sconf = s->module_config;
/*
* During create_proxy_config() we created a dummy id. Now that
* we have identifying info, we can create the real id
*/
(int)s->port,
s->defn_line_number,
/* Shared memory already created for this proxy_server_conf.
*/
s = s->next;
continue;
}
if (conf->bal_persist) {
} else {
}
if (rv != APR_SUCCESS) {
return !OK;
}
}
/* Initialize shared scoreboard data */
const char *sname;
/* now that we have the right id, we need to redo the sname field */
&sname);
/* Create global mutex */
"mutex creation of %s : %s failed", balancer_mutex_type,
return HTTP_INTERNAL_SERVER_ERROR;
}
/* setup shm for balancers */
return !OK;
}
return !OK;
}
return !OK;
}
/* create slotmem slots for workers */
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01184) "Doing workers create: %s (%s), %d, %d [%u]",
(int)balancer->max_workers, i);
if (rv != APR_SUCCESS) {
return !OK;
}
/* sync all timestamps */
/* now go thru each worker */
return !OK;
}
return !OK;
}
return !OK;
}
}
if (conf->bal_persist) {
/* We could have just read-in a persisted config. Force a sync. */
}
}
s = s->next;
}
return OK;
}
{
if (flag)
ap_rputs(" checked", r);
if (!flag)
ap_rputs(" checked", r);
ap_rputs("></td>\n", r);
}
const char *allowed[], apr_pool_t *p)
{
char *args;
char *key;
return;
}
while (key) {
if (val) {
*val++ = '\0';
}
else {
val = "";
}
}
else {
while (*ok) {
break;
}
ok++;
}
}
}
}
/* Manages the loadfactors and member status
* The balancer, worker and nonce are obtained from
* the request args (?b=...&w=...&nonce=....).
* All other params are pulled from any POST
* data that exists.
* TODO:
*/
static int balancer_handler(request_rec *r)
{
void *sconf;
int i, n;
int ok2change = 1;
const char *name;
const char *action;
/* is this for us? */
return DECLINED;
}
r->allowed = 0
| (AP_METHOD_BIT << M_GET)
| (AP_METHOD_BIT << M_POST);
return DECLINED;
}
"%s: Lock failed for balancer_handler",
}
"%s: Unlock failed for balancer_handler",
}
}
}
if (r->method_number == M_POST) {
if (rv != APR_SUCCESS) {
return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
/* Check that the supplied nonce matches this server's nonce;
* otherwise ignore all parameters, to prevent a CSRF attack. */
if (!bsel ||
(
)
)
) {
ok2change = 0;
}
/* First set the params */
const char *val;
if (bsel)
}
}
else
}
else
}
}
}
}
}
}
}
/* if enabling, we need to reset all lb params */
}
}
const char *val;
int ival;
"settings balancer params");
if (lbmethod) {
}
}
}
}
}
}
}
}
else {
char *path;
*path++ = '\0';
}
}
}
}
char *ret;
"%s: Lock failed for adding worker",
}
if (!ret) {
unsigned int index;
"worker slotmem_grab failed");
"%s: Unlock failed for adding worker",
}
return HTTP_BAD_REQUEST;
}
"worker slotmem_dptr failed");
"%s: Unlock failed for adding worker",
}
return HTTP_BAD_REQUEST;
}
"Cannot share worker");
"%s: Unlock failed for adding worker",
}
return HTTP_BAD_REQUEST;
}
"Cannot init worker");
"%s: Unlock failed for adding worker",
}
return HTTP_BAD_REQUEST;
}
/* sync all timestamps */
/* by default, all new workers are disabled */
}
"%s: Unlock failed for adding worker",
}
}
}
}
ap_set_content_type(r, "text/xml");
ap_rputs("<?xml version='1.0' encoding='UTF-8' ?>\n", r);
ap_rputs("<httpd:manager xmlns:httpd='http://httpd.apache.org'>\n", r);
ap_rputs(" <httpd:balancers>\n", r);
ap_rputs(" <httpd:balancer>\n", r);
ap_rputs(" <httpd:workers>\n", r);
ap_rputs(" <httpd:worker>\n", r);
"</httpd:scheme>\n", NULL);
"</httpd:hostname>\n", NULL);
ap_rprintf(r, " <httpd:loadfactor>%d</httpd:loadfactor>\n",
ap_rputs(" </httpd:worker>\n", r);
++workers;
}
ap_rputs(" </httpd:workers>\n", r);
ap_rputs(" </httpd:balancer>\n", r);
++balancer;
}
ap_rputs(" </httpd:balancers>\n", r);
ap_rputs("</httpd:manager>", r);
}
else {
ap_set_content_type(r, "text/html; charset=ISO-8859-1");
"<html><head><title>Balancer Manager</title>\n", r);
"table {\n"
" border-width: 1px;\n"
" border-spacing: 3px;\n"
" border-style: solid;\n"
" border-color: gray;\n"
" border-collapse: collapse;\n"
" background-color: white;\n"
" text-align: center;\n"
"}\n"
"th {\n"
" border-width: 1px;\n"
" padding: 2px;\n"
" border-style: dotted;\n"
" border-color: gray;\n"
" background-color: white;\n"
" text-align: center;\n"
"}\n"
"td {\n"
" border-width: 1px;\n"
" padding: 2px;\n"
" border-style: dotted;\n"
" border-color: gray;\n"
" background-color: white;\n"
" text-align: center;\n"
"}\n"
"</style>\n</head>\n", r);
ap_rputs("<body><h1>Load Balancer Manager for ", r);
ap_rvputs(r, "<dl><dt>Server Version: ",
ap_rvputs(r, "<dt>Server Built: ",
ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
"'>", NULL);
ap_rputs("\n\n<table><tr>"
"<th>MaxMembers</th><th>StickySession</th><th>DisableFailover</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
"<th>Path</th><th>Active</th></tr>\n<tr>", r);
/* the below is a safe cast, since the number of slots total will
* never be more than max_workers, which is restricted to int */
}
else {
}
}
else {
ap_rputs("<td> (None) ", r);
}
ap_rprintf(r, "<td>%s</td>\n",
ap_rprintf(r, "<td>%s</td>\n",
ap_rputs("<td>", r);
}
ap_rprintf(r, "<td>%s</td>\n",
ap_rputs("</table>\n<br />", r);
ap_rputs("\n\n<table><tr>"
"<th>Worker URL</th>"
"<th>Route</th><th>RouteRedir</th>"
"<th>Factor</th><th>Set</th><th>Status</th>"
"<th>Elected</th><th>Busy</th><th>Load</th><th>To</th><th>From</th>"
"</tr>\n", r);
char fbuf[50];
"'>", NULL);
NULL);
ap_rvputs(r, "</td><td>",
ap_rputs("</td>", r);
ap_rputs("</td><td>", r);
ap_rputs("</td></tr>\n", r);
++workers;
}
ap_rputs("</table>\n", r);
++balancer;
}
ap_rputs("<hr />\n", r);
ap_rputs("<h3>Edit worker settings for ", r);
ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action='", r);
ap_rputs("<dl>\n<table><tr><td>Load factor:</td><td><input name='w_lf' id='w_lf' type=text ", r);
ap_rputs("<tr><td>LB Set:</td><td><input name='w_ls' id='w_ls' type=text ", r);
ap_rputs("<tr><td>Route:</td><td><input name='w_wr' id='w_wr' type=text ", r);
NULL);
ap_rputs("'></td></tr>\n", r);
ap_rputs("<tr><td>Route Redirect:</td><td><input name='w_rr' id='w_rr' type=text ", r);
NULL);
ap_rputs("'></td></tr>\n", r);
ap_rputs("<tr><td>Status:</td>", r);
ap_rputs("<td><table><tr><th>Ign</th><th>Drn</th><th>Dis</th><th>Stby</th></tr>\n<tr>", r);
ap_rputs("</tr></table>\n", r);
ap_rputs("<tr><td colspan=2><input type=submit value='Submit'></td></tr>\n", r);
"'>\n", NULL);
ap_rvputs(r, "<input type=hidden name='nonce' id='nonce' value='",
ap_rputs("<hr />\n", r);
} else if (bsel) {
const apr_array_header_t *provs;
const ap_list_provider_names_t *pname;
int i;
ap_rputs("<h3>Edit balancer settings for ", r);
ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action='", r);
ap_rputs("<dl>\n<table>\n", r);
if (provs) {
ap_rputs("<tr><td>LBmethod:</td>", r);
ap_rputs("<td>\n<select name='b_lbm' id='b_lbm'>", r);
ap_rputs(" selected ", r);
}
ap_rputs("</select>\n</td></tr>\n", r);
}
ap_rputs("<tr><td>Timeout:</td><td><input name='b_tmo' id='b_tmo' type=text ", r);
ap_rputs("<tr><td>Failover Attempts:</td><td><input name='b_max' id='b_max' type=text ", r);
ap_rputs("<tr><td>Disable Failover:</td>", r);
ap_rputs("<tr><td>Sticky Session:</td><td><input name='b_ss' id='b_ss' size=64 type=text ", r);
}
else {
}
ap_rputs("'> (Use '-' to delete)</td></tr>\n", r);
ap_rputs("<tr><td>Add New Worker:</td><td><input name='b_nwrkr' id='b_nwrkr' size=32 type=text>"
" Are you sure? <input name='b_wyes' id='b_wyes' type=checkbox value='1'>"
"</td></tr>", r);
}
ap_rputs("<tr><td colspan=2><input type=submit value='Submit'></td></tr>\n", r);
"'>\n", NULL);
ap_rvputs(r, "<input type=hidden name='nonce' id='nonce' value='",
ap_rputs("<hr />\n", r);
}
ap_rputs("</body></html>\n", r);
ap_rflush(r);
}
return DONE;
}
{
while (s) {
int i;
void *sconf = s->module_config;
unsigned int num;
}
}
if (rv != APR_SUCCESS) {
"Failed to init balancer %s in child",
}
}
s = s->next;
}
}
static void ap_proxy_balancer_register_hook(apr_pool_t *p)
{
/* Only the mpm_winnt has child init hook handler.
* make sure that we are called after the mpm
* initializes
*/
/* manager handler */
}
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_proxy_balancer_register_hook /* register hooks */
};