mod_proxy_balancer.c revision 14c8c18e6caab1bdeb0f26b2b031e000fef58ef9
/* 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 "apr_hooks.h"
#include "apr_uuid.h"
#include "apr_date.h"
static const char *balancer_mutex_type = "proxy-balancer-shm";
/*
* 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;
}
"proxy: BALANCER: canonicalising URL %s", url);
/* 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) {
}
++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 it's 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 it's 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;
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) {
"proxy: BALANCER: Found value %s for "
}
else {
if (*route) {
"proxy: BALANCER: Found value %s for "
}
}
/*
* If we found a value for sticksession, find the first '.' within.
* Everything after '.' (if present) is our route.
*/
(*route)++;
"proxy: BALANCER: Found route %s", *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.
*/
"proxy: BALANCER: Route changed from %s to %s",
}
return worker;
}
else
return NULL;
}
request_rec *r)
{
return NULL;
}
if (candidate)
/*
PROXY_GLOBAL_UNLOCK(conf);
return NULL;
*/
}
/* 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 (!ok) {
/* If all workers are in error state force the recovery.
*/
"proxy: BALANCER: (%s). Forcing recovery for worker (%s)",
}
}
}
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
*/
"proxy: BALANCER: (%s). Lock failed for pre_request",
return DECLINED;
}
/* Step 3: force recovery */
/* Step 3.5: Update member list for the balancer */
/* TODO: Implement as provider! */
/* proxy_update_members(balancer, r, conf); */
/* 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) {
"proxy: BALANCER: (%s). All workers are in error state for route (%s)",
"proxy: BALANCER: (%s). Unlock failed for pre_request",
}
return HTTP_SERVICE_UNAVAILABLE;
}
}
"proxy: BALANCER: (%s). Unlock failed for pre_request",
}
if (!*worker) {
if (!runtime) {
"proxy: BALANCER: (%s). All workers are in error state",
} else {
"proxy: BALANCER: (%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);
}
"proxy: BALANCER (%s) worker (%s) rewritten to %s",
return access_status;
}
request_rec *r,
{
"proxy: BALANCER: (%s). Lock failed for post_request",
return HTTP_INTERNAL_SERVER_ERROR;
}
int i;
"proxy: BALANCER: (%s). Forcing recovery for worker (%s), failonstatus %d",
break;
}
}
}
"proxy: BALANCER: (%s). Unlock failed for post_request",
}
return OK;
}
{
int i;
/* Recalculate lbfactors */
/* Special case if there is only one worker it's
* 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 *data;
void *sconf = s->module_config;
const char *userdata_key = "mod_proxy_balancer_init";
/* balancer_post_config() will be called twice during startup. So, only
* set up the static data the 1st time through. */
if (!data) {
return OK;
}
/* Retrieve a UUID and store the nonce for the lifetime of
* the process. */
apr_uuid_get(&uuid);
/*
* Get worker slotmem setup
*/
if (!storage) {
"ap_lookup_provider %s failed", AP_SLOTMEM_PROVIDER_GROUP);
return !OK;
}
/*
* Go thru each Vhost and create the shared mem slotmem for
* each balancer's workers
*/
while (s) {
int i,j;
sconf = s->module_config;
/* Initialize shared scoreboard data */
/* Create global mutex */
"mutex creation of %s : %s failed", balancer_mutex_type,
return HTTP_INTERNAL_SERVER_ERROR;
}
(int)sizeof(proxy_worker_shared),
(int)balancer->max_workers);
if (rv != APR_SUCCESS) {
return !OK;
}
unsigned int index;
return !OK;
}
return !OK;
}
return !OK;
}
}
}
s = s->next;
}
return OK;
}
/* Manages the loadfactors and member status
*/
static int balancer_handler(request_rec *r)
{
void *sconf;
int access_status;
int i, n;
const char *name;
/* is this for us? */
return DECLINED;
}
if (r->method_number != M_GET) {
return DECLINED;
}
if (r->args) {
*val++ = '\0';
*tok++ = '\0';
/*
* Special case: workers are allowed path information
*/
return access_status;
}
else
return HTTP_BAD_REQUEST;
}
}
/* Check that the supplied nonce matches this server's nonce;
* otherwise ignore all parameters, to prevent a CSRF attack. */
if (*balancer_nonce &&
}
}
/* First set the params */
/*
* Note that it is not possible set the proxy_balancer because it is not
* in shared memory.
*/
if (wsel) {
const char *val;
if (bsel)
}
}
else
}
else
}
}
}
}
}
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></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);
ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
"<th>StickySession</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
"</tr>\n<tr>", r);
}
else {
}
}
else {
ap_rputs("<td> - ", r);
}
ap_rprintf(r, "<td>%s</td>\n",
ap_rputs("</table>\n<br />", r);
ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
"<th>Worker URL</th>"
"<th>Route</th><th>RouteRedir</th>"
"<th>Factor</th><th>Set</th><th>Status</th>"
"<th>Elected</th><th>To</th><th>From</th>"
"</tr>\n", r);
char fbuf[50];
"&nonce=", balancer_nonce,
"\">", NULL);
NULL);
ap_rvputs(r, "</td><td>",
ap_rputs("Dis ", r);
ap_rputs("Err ", r);
ap_rputs("Stop ", r);
ap_rputs("Stby ", r);
if (PROXY_WORKER_IS_USABLE(worker))
ap_rputs("Ok", r);
ap_rputs("-", r);
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("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
ap_rputs("<tr><td>LB Set:</td><td><input name=\"ls\" type=text ", r);
ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
NULL);
ap_rputs("\"></td></tr>\n", r);
ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
NULL);
ap_rputs("\"></td></tr>\n", r);
ap_rputs("<tr><td>Status:</td><td>Disabled: <input name=\"dw\" value=\"Disable\" type=radio", r);
ap_rputs(" checked", r);
ap_rputs("> | Enabled: <input name=\"dw\" value=\"Enable\" type=radio", r);
ap_rputs(" checked", r);
ap_rputs("></td></tr>\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\" value=\"",
ap_rputs("<hr />\n", r);
}
ap_rputs("</body></html>\n", r);
}
return OK;
}
{
while (s) {
int i;
void *sconf = s->module_config;
unsigned int num;
/*
* for each balancer we need to init the global
* mutex and then attach to the shared worker shm
*/
return;
}
/* Re-open the mutex for the child. */
p);
if (rv != APR_SUCCESS) {
"Failed to reopen mutex %: %s in child",
}
/* now attach */
}
balancer++;
}
s = s->next;
}
}
const char *val)
{
return err;
}
*balancer_nonce = '\0';
if (val) {
} else {
return "BalancerNonce Set requires an argument";
}
return "Bad argument for BalancerNonce: Must be 'Set', 'None' or 'Default'";
}
return NULL;
}
static const command_rec balancer_cmds[] =
{
RSRC_CONF, "Set value for balancer-manager nonce"),
{NULL}
};
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 */
balancer_cmds, /* command apr_table_t */
ap_proxy_balancer_register_hook /* register hooks */
};