]> granicus.if.org Git - apache/blob - modules/proxy/mod_proxy_balancer.c
Add in a very simple balancer "set" concept, which allows
[apache] / modules / proxy / mod_proxy_balancer.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* Load balancer module for Apache proxy */
18
19 #define CORE_PRIVATE
20
21 #include "mod_proxy.h"
22 #include "ap_mpm.h"
23 #include "apr_version.h"
24 #include "apr_hooks.h"
25
26 #if APR_HAVE_UNISTD_H
27 #include <unistd.h> /* for getpid() */
28 #endif
29
30 module AP_MODULE_DECLARE_DATA proxy_balancer_module;
31
32 static int proxy_balancer_canon(request_rec *r, char *url)
33 {
34     char *host, *path, *search;
35     const char *err;
36     apr_port_t port = 0;
37
38     if (strncasecmp(url, "balancer:", 9) == 0) {
39         url += 9;
40     }
41     else {
42         return DECLINED;
43     }
44
45     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
46              "proxy: BALANCER: canonicalising URL %s", url);
47
48     /* do syntatic check.
49      * We break the URL into host, port, path, search
50      */
51     err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
52     if (err) {
53         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
54                       "error parsing URL %s: %s",
55                       url, err);
56         return HTTP_BAD_REQUEST;
57     }
58     /* now parse path/search args, according to rfc1738 */
59     /* N.B. if this isn't a true proxy request, then the URL _path_
60      * has already been decoded.  True proxy requests have r->uri
61      * == r->unparsed_uri, and no others have that property.
62      */
63     if (r->uri == r->unparsed_uri) {
64         search = strchr(url, '?');
65         if (search != NULL)
66             *(search++) = '\0';
67     }
68     else
69         search = r->args;
70
71     /* process path */
72     path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, r->proxyreq);
73     if (path == NULL)
74         return HTTP_BAD_REQUEST;
75
76     r->filename = apr_pstrcat(r->pool, "proxy:balancer://", host,
77             "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
78
79     r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
80
81     return OK;
82 }
83
84 static int init_balancer_members(proxy_server_conf *conf, server_rec *s,
85                                  proxy_balancer *balancer)
86 {
87     int i;
88     proxy_worker *workers;
89
90     workers = (proxy_worker *)balancer->workers->elts;
91
92     for (i = 0; i < balancer->workers->nelts; i++) {
93         ap_proxy_initialize_worker_share(conf, workers, s);
94         ap_proxy_initialize_worker(workers, s);
95         ++workers;
96     }
97
98     workers = (proxy_worker *)balancer->workers->elts;
99     for (i = 0; i < balancer->workers->nelts; i++) {
100         /* Set to the original configuration */
101         workers[i].s->lbstatus = workers[i].s->lbfactor =
102           (workers[i].lbfactor ? workers[i].lbfactor : 1);
103         workers[i].s->lbset = workers[i].lbset;
104     }
105     /* Set default number of attempts to the number of
106      * workers.
107      */
108     if (!balancer->max_attempts_set && balancer->workers->nelts > 1) {
109         balancer->max_attempts = balancer->workers->nelts - 1;
110         balancer->max_attempts_set = 1;
111     }
112     return 0;
113 }
114
115 /* Retrieve the parameter with the given name
116  * Something like 'JSESSIONID=12345...N'
117  */
118 static char *get_path_param(apr_pool_t *pool, char *url,
119                             const char *name)
120 {
121     char *path = NULL;
122
123     for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
124         path += strlen(name);
125         if (*path == '=') {
126             /*
127              * Session path was found, get it's value
128              */
129             ++path;
130             if (strlen(path)) {
131                 char *q;
132                 path = apr_pstrdup(pool, path);
133                 if ((q = strchr(path, '?')))
134                     *q = '\0';
135                 return path;
136             }
137         }
138     }
139     return NULL;
140 }
141
142 static char *get_cookie_param(request_rec *r, const char *name)
143 {
144     const char *cookies;
145     const char *start_cookie;
146
147     if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
148         for (start_cookie = ap_strstr_c(cookies, name); start_cookie;
149              start_cookie = ap_strstr_c(start_cookie + 1, name)) {
150             if (start_cookie == cookies ||
151                 start_cookie[-1] == ';' ||
152                 start_cookie[-1] == ',' ||
153                 isspace(start_cookie[-1])) {
154
155                 start_cookie += strlen(name);
156                 while(*start_cookie && isspace(*start_cookie))
157                     ++start_cookie;
158                 if (*start_cookie == '=' && start_cookie[1]) {
159                     /*
160                      * Session cookie was found, get it's value
161                      */
162                     char *end_cookie, *cookie;
163                     ++start_cookie;
164                     cookie = apr_pstrdup(r->pool, start_cookie);
165                     if ((end_cookie = strchr(cookie, ';')) != NULL)
166                         *end_cookie = '\0';
167                     if((end_cookie = strchr(cookie, ',')) != NULL)
168                         *end_cookie = '\0';
169                     return cookie;
170                 }
171             }
172         }
173     }
174     return NULL;
175 }
176
177 /* Find the worker that has the 'route' defined
178  */
179 static proxy_worker *find_route_worker(proxy_balancer *balancer,
180                                        const char *route, request_rec *r)
181 {
182     int i;
183     int checking_standby = 0;
184     int checked_standby = 0;
185     
186     proxy_worker *worker;
187     while (!checked_standby) {
188         worker = (proxy_worker *)balancer->workers->elts;
189         for (i = 0; i < balancer->workers->nelts; i++, worker++) {
190             if ( (checking_standby ? !PROXY_WORKER_IS_STANDBY(worker) : PROXY_WORKER_IS_STANDBY(worker)) )
191                 continue;
192             if (*(worker->s->route) && strcmp(worker->s->route, route) == 0) {
193                 if (worker && PROXY_WORKER_IS_USABLE(worker)) {
194                     return worker;
195                 } else {
196                     /*
197                      * If the worker is in error state run
198                      * retry on that worker. It will be marked as
199                      * operational if the retry timeout is elapsed.
200                      * The worker might still be unusable, but we try
201                      * anyway.
202                      */
203                     ap_proxy_retry_worker("BALANCER", worker, r->server);
204                     if (PROXY_WORKER_IS_USABLE(worker)) {
205                             return worker;
206                     } else {
207                         /*
208                          * We have a worker that is unusable.
209                          * It can be in error or disabled, but in case
210                          * it has a redirection set use that redirection worker.
211                          * This enables to safely remove the member from the
212                          * balancer. Of course you will need some kind of
213                          * session replication between those two remote.
214                          */
215                         if (*worker->s->redirect) {
216                             proxy_worker *rworker = NULL;
217                             rworker = find_route_worker(balancer, worker->s->redirect, r);
218                             /* Check if the redirect worker is usable */
219                             if (rworker && !PROXY_WORKER_IS_USABLE(rworker)) {
220                                 /*
221                                  * If the worker is in error state run
222                                  * retry on that worker. It will be marked as
223                                  * operational if the retry timeout is elapsed.
224                                  * The worker might still be unusable, but we try
225                                  * anyway.
226                                  */
227                                 ap_proxy_retry_worker("BALANCER", rworker, r->server);
228                             }
229                             if (rworker && PROXY_WORKER_IS_USABLE(rworker))
230                                 return rworker;
231                         }
232                     }
233                 }
234             }
235         }
236         checked_standby = checking_standby++;
237     }
238     return NULL;
239 }
240
241 static proxy_worker *find_session_route(proxy_balancer *balancer,
242                                         request_rec *r,
243                                         char **route,
244                                         char **url)
245 {
246     proxy_worker *worker = NULL;
247
248     if (!balancer->sticky)
249         return NULL;
250     /* Try to find the sticky route inside url */
251     *route = get_path_param(r->pool, *url, balancer->sticky);
252     if (!*route)
253         *route = get_cookie_param(r, balancer->sticky);
254     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
255                             "proxy: BALANCER: Found value %s for "
256                             "stickysession %s", *route, balancer->sticky);
257     /*
258      * If we found a value for sticksession, find the first '.' within.
259      * Everything after '.' (if present) is our route.
260      */
261     if ((*route) && ((*route = strchr(*route, '.')) != NULL ))
262         (*route)++;
263     if ((*route) && (**route)) {
264         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
265                                   "proxy: BALANCER: Found route %s", *route);
266         /* We have a route in path or in cookie
267          * Find the worker that has this route defined.
268          */
269         worker = find_route_worker(balancer, *route, r);
270         return worker;
271     }
272     else
273         return NULL;
274 }
275
276 static proxy_worker *find_best_worker(proxy_balancer *balancer,
277                                       request_rec *r)
278 {
279     proxy_worker *candidate = NULL;
280
281     if (PROXY_THREAD_LOCK(balancer) != APR_SUCCESS)
282         return NULL;
283
284     candidate = (*balancer->lbmethod->finder)(balancer, r);
285
286 /*
287         PROXY_THREAD_UNLOCK(balancer);
288         return NULL;
289 */
290
291     PROXY_THREAD_UNLOCK(balancer);
292
293     if (candidate == NULL) {
294         /* All the workers are in error state or disabled.
295          * If the balancer has a timeout sleep for a while
296          * and try again to find the worker. The chances are
297          * that some other thread will release a connection.
298          * By default the timeout is not set, and the server
299          * returns SERVER_BUSY.
300          */
301 #if APR_HAS_THREADS
302         if (balancer->timeout) {
303             /* XXX: This can perhaps be build using some
304              * smarter mechanism, like tread_cond.
305              * But since the statuses can came from
306              * different childs, use the provided algo.
307              */
308             apr_interval_time_t timeout = balancer->timeout;
309             apr_interval_time_t step, tval = 0;
310             /* Set the timeout to 0 so that we don't
311              * end in infinite loop
312              */
313             balancer->timeout = 0;
314             step = timeout / 100;
315             while (tval < timeout) {
316                 apr_sleep(step);
317                 /* Try again */
318                 if ((candidate = find_best_worker(balancer, r)))
319                     break;
320                 tval += step;
321             }
322             /* restore the timeout */
323             balancer->timeout = timeout;
324         }
325 #endif
326     }
327     return candidate;
328 }
329
330 static int rewrite_url(request_rec *r, proxy_worker *worker,
331                         char **url)
332 {
333     const char *scheme = strstr(*url, "://");
334     const char *path = NULL;
335
336     if (scheme)
337         path = ap_strchr_c(scheme + 3, '/');
338
339     /* we break the URL into host, port, uri */
340     if (!worker) {
341         return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
342                              "missing worker. URI cannot be parsed: ", *url,
343                              NULL));
344     }
345
346     *url = apr_pstrcat(r->pool, worker->name, path, NULL);
347
348     return OK;
349 }
350
351 static int proxy_balancer_pre_request(proxy_worker **worker,
352                                       proxy_balancer **balancer,
353                                       request_rec *r,
354                                       proxy_server_conf *conf, char **url)
355 {
356     int access_status;
357     proxy_worker *runtime;
358     char *route = NULL;
359     apr_status_t rv;
360
361     *worker = NULL;
362     /* Step 1: check if the url is for us
363      * The url we can handle starts with 'balancer://'
364      * If balancer is already provided skip the search
365      * for balancer, because this is failover attempt.
366      */
367     if (!*balancer &&
368         !(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
369         return DECLINED;
370
371     /* Step 2: find the session route */
372
373     runtime = find_session_route(*balancer, r, &route, url);
374     /* Lock the LoadBalancer
375      * XXX: perhaps we need the process lock here
376      */
377     if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
378         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
379                      "proxy: BALANCER: lock");
380         return DECLINED;
381     }
382     if (runtime) {
383         int i, total_factor = 0;
384         proxy_worker *workers;
385         /* We have a sticky load balancer
386          * Update the workers status
387          * so that even session routes get
388          * into account.
389          */
390         workers = (proxy_worker *)(*balancer)->workers->elts;
391         for (i = 0; i < (*balancer)->workers->nelts; i++) {
392             /* Take into calculation only the workers that are
393              * not in error state or not disabled.
394              *
395              * TODO: Abstract the below, since this is dependent
396              *       on the LB implementation
397              */
398             if (PROXY_WORKER_IS_USABLE(workers)) {
399                 workers->s->lbstatus += workers->s->lbfactor;
400                 total_factor += workers->s->lbfactor;
401             }
402             workers++;
403         }
404         runtime->s->lbstatus -= total_factor;
405         runtime->s->elected++;
406
407         *worker = runtime;
408     }
409     else if (route && (*balancer)->sticky_force) {
410         ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
411                      "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
412                      (*balancer)->name, route);
413         PROXY_THREAD_UNLOCK(*balancer);
414         return HTTP_SERVICE_UNAVAILABLE;
415     }
416
417     PROXY_THREAD_UNLOCK(*balancer);
418     if (!*worker) {
419         runtime = find_best_worker(*balancer, r);
420         if (!runtime) {
421             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
422                          "proxy: BALANCER: (%s). All workers are in error state",
423                          (*balancer)->name);
424
425             return HTTP_SERVICE_UNAVAILABLE;
426         }
427         *worker = runtime;
428     }
429
430     /* Add balancer/worker info to env. */
431     apr_table_setn(r->subprocess_env,
432                    "BALANCER_NAME", (*balancer)->name);
433     apr_table_setn(r->subprocess_env,
434                    "BALANCER_WORKER_NAME", (*worker)->name);
435     apr_table_setn(r->subprocess_env,
436                    "BALANCER_WORKER_ROUTE", (*worker)->s->route);
437
438     /* Rewrite the url from 'balancer://url'
439      * to the 'worker_scheme://worker_hostname[:worker_port]/url'
440      * This replaces the balancers fictional name with the
441      * real hostname of the elected worker.
442      */
443     access_status = rewrite_url(r, *worker, url);
444     /* Add the session route to request notes if present */
445     if (route) {
446         apr_table_setn(r->notes, "session-sticky", (*balancer)->sticky);
447         apr_table_setn(r->notes, "session-route", route);
448
449         /* Add session info to env. */
450         apr_table_setn(r->subprocess_env,
451                        "BALANCER_SESSION_STICKY", (*balancer)->sticky);
452         apr_table_setn(r->subprocess_env,
453                        "BALANCER_SESSION_ROUTE", route);
454     }
455     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
456                  "proxy: BALANCER (%s) worker (%s) rewritten to %s",
457                  (*balancer)->name, (*worker)->name, *url);
458
459     return access_status;
460 }
461
462 static int proxy_balancer_post_request(proxy_worker *worker,
463                                        proxy_balancer *balancer,
464                                        request_rec *r,
465                                        proxy_server_conf *conf)
466 {
467     apr_status_t rv;
468
469     if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
470         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
471             "proxy: BALANCER: lock");
472         return HTTP_INTERNAL_SERVER_ERROR;
473     }
474     /* TODO: calculate the bytes transferred
475      * This will enable to elect the worker that has
476      * the lowest load.
477      * The bytes transferred depends on the protocol
478      * used, so each protocol handler should keep the
479      * track on that.
480      */
481
482     PROXY_THREAD_UNLOCK(balancer);
483     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
484                  "proxy_balancer_post_request for (%s)", balancer->name);
485
486     return OK;
487 }
488
489 static void recalc_factors(proxy_balancer *balancer)
490 {
491     int i;
492     proxy_worker *workers;
493
494
495     /* Recalculate lbfactors */
496     workers = (proxy_worker *)balancer->workers->elts;
497     /* Special case if there is only one worker it's
498      * load factor will always be 1
499      */
500     if (balancer->workers->nelts == 1) {
501         workers->s->lbstatus = workers->s->lbfactor = 1;
502         return;
503     }
504     for (i = 0; i < balancer->workers->nelts; i++) {
505         /* Update the status entries */
506         workers[i].s->lbstatus = workers[i].s->lbfactor;
507     }
508 }
509
510 /* Manages the loadfactors and member status
511  */
512 static int balancer_handler(request_rec *r)
513 {
514     void *sconf = r->server->module_config;
515     proxy_server_conf *conf = (proxy_server_conf *)
516         ap_get_module_config(sconf, &proxy_module);
517     proxy_balancer *balancer, *bsel = NULL;
518     proxy_worker *worker, *wsel = NULL;
519     apr_table_t *params = apr_table_make(r->pool, 10);
520     int access_status;
521     int i, n;
522     const char *name;
523
524     /* is this for us? */
525     if (strcmp(r->handler, "balancer-manager"))
526         return DECLINED;
527     r->allowed = (AP_METHOD_BIT << M_GET);
528     if (r->method_number != M_GET)
529         return DECLINED;
530
531     if (r->args) {
532         char *args = apr_pstrdup(r->pool, r->args);
533         char *tok, *val;
534         while (args && *args) {
535             if ((val = ap_strchr(args, '='))) {
536                 *val++ = '\0';
537                 if ((tok = ap_strchr(val, '&')))
538                     *tok++ = '\0';
539                 /*
540                  * Special case: workers are allowed path information
541                  */
542                 if ((access_status = ap_unescape_url(val)) != OK)
543                     if (strcmp(args, "w") || (access_status !=  HTTP_NOT_FOUND))
544                         return access_status;
545                 apr_table_setn(params, args, val);
546                 args = tok;
547             }
548             else
549                 return HTTP_BAD_REQUEST;
550         }
551     }
552     if ((name = apr_table_get(params, "b")))
553         bsel = ap_proxy_get_balancer(r->pool, conf,
554             apr_pstrcat(r->pool, "balancer://", name, NULL));
555     if ((name = apr_table_get(params, "w"))) {
556         proxy_worker *ws;
557
558         ws = ap_proxy_get_worker(r->pool, conf, name);
559         if (ws) {
560             worker = (proxy_worker *)bsel->workers->elts;
561             for (n = 0; n < bsel->workers->nelts; n++) {
562                 if (strcasecmp(worker->name, ws->name) == 0) {
563                     wsel = worker;
564                     break;
565                 }
566                 ++worker;
567             }
568         }
569     }
570     /* First set the params */
571     if (bsel) {
572         const char *val;
573         if ((val = apr_table_get(params, "ss"))) {
574             if (strlen(val))
575                 bsel->sticky = apr_pstrdup(conf->pool, val);
576             else
577                 bsel->sticky = NULL;
578         }
579         if ((val = apr_table_get(params, "tm"))) {
580             int ival = atoi(val);
581             if (ival >= 0)
582                 bsel->timeout = apr_time_from_sec(ival);
583         }
584         if ((val = apr_table_get(params, "fa"))) {
585             int ival = atoi(val);
586             if (ival >= 0)
587                 bsel->max_attempts = ival;
588             bsel->max_attempts_set = 1;
589         }
590         if ((val = apr_table_get(params, "lm"))) {
591             proxy_balancer_method *provider;
592             provider = ap_lookup_provider(PROXY_LBMETHOD, val, "0");
593             if (provider) {
594                 bsel->lbmethod = provider;
595             }
596         }
597     }
598     if (wsel) {
599         const char *val;
600         if ((val = apr_table_get(params, "lf"))) {
601             int ival = atoi(val);
602             if (ival >= 1 && ival <= 100) {
603                 wsel->s->lbfactor = ival;
604                 if (bsel)
605                     recalc_factors(bsel);
606             }
607         }
608         if ((val = apr_table_get(params, "wr"))) {
609             if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
610                 strcpy(wsel->s->route, val);
611             else
612                 *wsel->s->route = '\0';
613         }
614         if ((val = apr_table_get(params, "rr"))) {
615             if (strlen(val) && strlen(val) < PROXY_WORKER_MAX_ROUTE_SIZ)
616                 strcpy(wsel->s->redirect, val);
617             else
618                 *wsel->s->redirect = '\0';
619         }
620         if ((val = apr_table_get(params, "dw"))) {
621             if (!strcasecmp(val, "Disable"))
622                 wsel->s->status |= PROXY_WORKER_DISABLED;
623             else if (!strcasecmp(val, "Enable"))
624                 wsel->s->status &= ~PROXY_WORKER_DISABLED;
625         }
626         if ((val = apr_table_get(params, "ls"))) {
627             int ival = atoi(val);
628             if (ival >= 0 && ival <= 99) {
629                 wsel->s->lbset = ival;
630              }
631         }
632
633     }
634     if (apr_table_get(params, "xml")) {
635         ap_set_content_type(r, "text/xml");
636         ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", r);
637         ap_rputs("<httpd:manager xmlns:httpd=\"http://httpd.apache.org\">\n", r);
638         ap_rputs("  <httpd:balancers>\n", r);
639         balancer = (proxy_balancer *)conf->balancers->elts;
640         for (i = 0; i < conf->balancers->nelts; i++) {
641             ap_rputs("    <httpd:balancer>\n", r);
642             ap_rvputs(r, "      <httpd:name>", balancer->name, "</httpd:name>\n", NULL);
643             ap_rputs("      <httpd:workers>\n", r);
644             worker = (proxy_worker *)balancer->workers->elts;
645             for (n = 0; n < balancer->workers->nelts; n++) {
646                 ap_rputs("        <httpd:worker>\n", r);
647                 ap_rvputs(r, "          <httpd:scheme>", worker->scheme,
648                           "</httpd:scheme>\n", NULL);
649                 ap_rvputs(r, "          <httpd:hostname>", worker->hostname,
650                           "</httpd:hostname>\n", NULL);
651                ap_rprintf(r, "          <httpd:loadfactor>%d</httpd:loadfactor>\n",
652                           worker->s->lbfactor);
653                 ap_rputs("        </httpd:worker>\n", r);
654                 ++worker;
655             }
656             ap_rputs("      </httpd:workers>\n", r);
657             ap_rputs("    </httpd:balancer>\n", r);
658             ++balancer;
659         }
660         ap_rputs("  </httpd:balancers>\n", r);
661         ap_rputs("</httpd:manager>", r);
662     }
663     else {
664         ap_set_content_type(r, "text/html");
665         ap_rputs(DOCTYPE_HTML_3_2
666                  "<html><head><title>Balancer Manager</title></head>\n", r);
667         ap_rputs("<body><h1>Load Balancer Manager for ", r);
668         ap_rvputs(r, ap_get_server_name(r), "</h1>\n\n", NULL);
669         ap_rvputs(r, "<dl><dt>Server Version: ",
670                   ap_get_server_version(), "</dt>\n", NULL);
671         ap_rvputs(r, "<dt>Server Built: ",
672                   ap_get_server_built(), "\n</dt></dl>\n", NULL);
673         balancer = (proxy_balancer *)conf->balancers->elts;
674         for (i = 0; i < conf->balancers->nelts; i++) {
675
676             ap_rputs("<hr />\n<h3>LoadBalancer Status for ", r);
677             ap_rvputs(r, "<a href=\"", r->uri, "?b=",
678                       balancer->name + sizeof("balancer://") - 1,
679                       "\">", NULL);
680             ap_rvputs(r, balancer->name, "</a></h3>\n\n", NULL);
681             ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
682                 "<th>StickySession</th><th>Timeout</th><th>FailoverAttempts</th><th>Method</th>"
683                 "</tr>\n<tr>", r);
684             ap_rvputs(r, "<td>", balancer->sticky, NULL);
685             ap_rprintf(r, "</td><td>%" APR_TIME_T_FMT "</td>",
686                 apr_time_sec(balancer->timeout));
687             ap_rprintf(r, "<td>%d</td>\n", balancer->max_attempts);
688             ap_rprintf(r, "<td>%s</td>\n",
689                        balancer->lbmethod->name);
690             ap_rputs("</table>\n<br />", r);
691             ap_rputs("\n\n<table border=\"0\" style=\"text-align: left;\"><tr>"
692                 "<th>Worker URL</th>"
693                 "<th>Route</th><th>RouteRedir</th>"
694                 "<th>Factor</th><th>Set</th><th>Status</th>"
695                 "<th>Elected</th><th>To</th><th>From</th>"
696                 "</tr>\n", r);
697
698             worker = (proxy_worker *)balancer->workers->elts;
699             for (n = 0; n < balancer->workers->nelts; n++) {
700                 char fbuf[50];
701                 ap_rvputs(r, "<tr>\n<td><a href=\"", r->uri, "?b=",
702                           balancer->name + sizeof("balancer://") - 1, "&w=",
703                           ap_escape_uri(r->pool, worker->name),
704                           "\">", NULL);
705                 ap_rvputs(r, worker->name, "</a></td>", NULL);
706                 ap_rvputs(r, "<td>", worker->s->route, NULL);
707                 ap_rvputs(r, "</td><td>", worker->s->redirect, NULL);
708                 ap_rprintf(r, "</td><td>%d</td>", worker->s->lbfactor);
709                 ap_rprintf(r, "<td>%d</td><td>", worker->s->lbset);
710                 if (worker->s->status & PROXY_WORKER_DISABLED)
711                    ap_rputs("Dis ", r);
712                 if (worker->s->status & PROXY_WORKER_IN_ERROR)
713                    ap_rputs("Err ", r);
714                 if (worker->s->status & PROXY_WORKER_STOPPED)
715                    ap_rputs("Stop ", r);
716                 if (worker->s->status & PROXY_WORKER_HOT_STANDBY)
717                    ap_rputs("Stby ", r);
718                 if (PROXY_WORKER_IS_USABLE(worker))
719                     ap_rputs("Ok", r);
720                 if (!PROXY_WORKER_IS_INITIALIZED(worker))
721                     ap_rputs("-", r);
722                 ap_rputs("</td>", r);
723                 ap_rprintf(r, "<td>%" APR_SIZE_T_FMT "</td><td>", worker->s->elected);
724                 ap_rputs(apr_strfsize(worker->s->transferred, fbuf), r);
725                 ap_rputs("</td><td>", r);
726                 ap_rputs(apr_strfsize(worker->s->read, fbuf), r);
727                 ap_rputs("</td></tr>\n", r);
728
729                 ++worker;
730             }
731             ap_rputs("</table>\n", r);
732             ++balancer;
733         }
734         ap_rputs("<hr />\n", r);
735         if (wsel && bsel) {
736             ap_rputs("<h3>Edit worker settings for ", r);
737             ap_rvputs(r, wsel->name, "</h3>\n", NULL);
738             ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
739             ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
740             ap_rputs("<table><tr><td>Load factor:</td><td><input name=\"lf\" type=text ", r);
741             ap_rprintf(r, "value=\"%d\"></td></tr>\n", wsel->s->lbfactor);
742             ap_rputs("<tr><td>LB Set:</td><td><input name=\"ls\" type=text ", r);
743             ap_rprintf(r, "value=\"%d\"></td></tr>\n", wsel->s->lbset);
744             ap_rputs("<tr><td>Route:</td><td><input name=\"wr\" type=text ", r);
745             ap_rvputs(r, "value=\"", wsel->route, NULL);
746             ap_rputs("\"></td></tr>\n", r);
747             ap_rputs("<tr><td>Route Redirect:</td><td><input name=\"rr\" type=text ", r);
748             ap_rvputs(r, "value=\"", wsel->redirect, NULL);
749             ap_rputs("\"></td></tr>\n", r);
750             ap_rputs("<tr><td>Status:</td><td>Disabled: <input name=\"dw\" value=\"Disable\" type=radio", r);
751             if (wsel->s->status & PROXY_WORKER_DISABLED)
752                 ap_rputs(" checked", r);
753             ap_rputs("> | Enabled: <input name=\"dw\" value=\"Enable\" type=radio", r);
754             if (!(wsel->s->status & PROXY_WORKER_DISABLED))
755                 ap_rputs(" checked", r);
756             ap_rputs("></td></tr>\n", r);
757             ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
758             ap_rvputs(r, "</table>\n<input type=hidden name=\"w\" ",  NULL);
759             ap_rvputs(r, "value=\"", ap_escape_uri(r->pool, wsel->name), "\">\n", NULL);
760             ap_rvputs(r, "<input type=hidden name=\"b\" ", NULL);
761             ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
762                       "\">\n</form>\n", NULL);
763             ap_rputs("<hr />\n", r);
764         }
765         else if (bsel) {
766             ap_rputs("<h3>Edit balancer settings for ", r);
767             ap_rvputs(r, bsel->name, "</h3>\n", NULL);
768             ap_rvputs(r, "<form method=\"GET\" action=\"", NULL);
769             ap_rvputs(r, r->uri, "\">\n<dl>", NULL);
770             ap_rputs("<table><tr><td>StickySession Identifier:</td><td><input name=\"ss\" type=text ", r);
771             if (bsel->sticky)
772                 ap_rvputs(r, "value=\"", bsel->sticky, "\"", NULL);
773             ap_rputs("></td><tr>\n<tr><td>Timeout:</td><td><input name=\"tm\" type=text ", r);
774             ap_rprintf(r, "value=\"%" APR_TIME_T_FMT "\"></td></tr>\n",
775                        apr_time_sec(bsel->timeout));
776             ap_rputs("<tr><td>Failover Attempts:</td><td><input name=\"fa\" type=text ", r);
777             ap_rprintf(r, "value=\"%d\"></td></tr>\n",
778                        bsel->max_attempts);
779             ap_rputs("<tr><td>LB Method:</td><td><select name=\"lm\">", r);
780             {
781                 apr_array_header_t *methods;
782                 ap_list_provider_names_t *method;
783                 int i;
784                 methods = ap_list_provider_names(r->pool, PROXY_LBMETHOD, "0");
785                 method = (ap_list_provider_names_t *)methods->elts;
786                 for (i = 0; i < methods->nelts; i++) {
787                     ap_rprintf(r, "<option value=\"%s\" %s>%s</option>", method->provider_name,
788                        (!strcasecmp(bsel->lbmethod->name, method->provider_name)) ? "selected" : "",
789                        method->provider_name);
790                     method++;
791                 }
792             }
793             ap_rputs("</select></td></tr>\n", r);
794             ap_rputs("<tr><td colspan=2><input type=submit value=\"Submit\"></td></tr>\n", r);
795             ap_rvputs(r, "</table>\n<input type=hidden name=\"b\" ", NULL);
796             ap_rvputs(r, "value=\"", bsel->name + sizeof("balancer://") - 1,
797                       "\">\n</form>\n", NULL);
798             ap_rputs("<hr />\n", r);
799         }
800         ap_rputs(ap_psignature("",r), r);
801         ap_rputs("</body></html>\n", r);
802     }
803     return OK;
804 }
805
806 static void child_init(apr_pool_t *p, server_rec *s)
807 {
808     while (s) {
809         void *sconf = s->module_config;
810         proxy_server_conf *conf;
811         proxy_balancer *balancer;
812         int i;
813         conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module);
814
815         /* Initialize shared scoreboard data */
816         balancer = (proxy_balancer *)conf->balancers->elts;
817         for (i = 0; i < conf->balancers->nelts; i++) {
818             init_balancer_members(conf, s, balancer);
819             balancer++;
820         }
821         s = s->next;
822     }
823
824 }
825
826 /*
827  * The idea behind the find_best_byrequests scheduler is the following:
828  *
829  * lbfactor is "how much we expect this worker to work", or "the worker's
830  * normalized work quota".
831  *
832  * lbstatus is "how urgent this worker has to work to fulfill its quota
833  * of work".
834  *
835  * We distribute each worker's work quota to the worker, and then look
836  * which of them needs to work most urgently (biggest lbstatus).  This
837  * worker is then selected for work, and its lbstatus reduced by the
838  * total work quota we distributed to all workers.  Thus the sum of all
839  * lbstatus does not change.(*)
840  *
841  * If some workers are disabled, the others will
842  * still be scheduled correctly.
843  *
844  * If a balancer is configured as follows:
845  *
846  * worker     a    b    c    d
847  * lbfactor  25   25   25   25
848  *
849  * And b gets disabled, the following schedule is produced:
850  *
851  *    a c d a c d a c d ...
852  *
853  * Note that the above lbfactor setting is the *exact* same as:
854  *
855  * worker     a    b    c    d
856  * lbfactor   1    1    1    1
857  *
858  * Asymmetric configurations work as one would expect. For
859  * example:
860  *
861  * worker     a    b    c    d
862  * lbfactor   1    1    1    2
863  *
864  * would have a, b and c all handling about the same
865  * amount of load with d handling twice what a or b
866  * or c handles individually. So we could see:
867  *
868  *   b a d c d a c d b d ...
869  *
870  */
871
872 static proxy_worker *find_best_byrequests(proxy_balancer *balancer,
873                                 request_rec *r)
874 {
875     int i;
876     int total_factor = 0;
877     proxy_worker *worker;
878     proxy_worker *mycandidate = NULL;
879     int checking_standby = 0;
880     int checked_standby = 0;
881     int cur_lbset = 0;
882     int max_lbset = 0;
883     
884     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
885                  "proxy: Entering byrequests for BALANCER (%s)",
886                  balancer->name);
887
888     /* First try to see if we have available candidate */
889     do {
890         while (!mycandidate && !checked_standby) {
891             worker = (proxy_worker *)balancer->workers->elts;
892             for (i = 0; i < balancer->workers->nelts; i++, worker++) {
893                 if (!checking_standby) {    /* first time through */
894                     if (worker->s->lbset > max_lbset)
895                         max_lbset = worker->s->lbset;
896                 }
897                 if (worker->s->lbset > cur_lbset)
898                     continue;
899                 if ( (checking_standby ? !PROXY_WORKER_IS_STANDBY(worker) : PROXY_WORKER_IS_STANDBY(worker)) )
900                     continue;
901                 /* If the worker is in error state run
902                  * retry on that worker. It will be marked as
903                  * operational if the retry timeout is elapsed.
904                  * The worker might still be unusable, but we try
905                  * anyway.
906                  */
907                 if (!PROXY_WORKER_IS_USABLE(worker))
908                     ap_proxy_retry_worker("BALANCER", worker, r->server);
909                 /* Take into calculation only the workers that are
910                  * not in error state or not disabled.
911                  */
912                 if (PROXY_WORKER_IS_USABLE(worker)) {
913                     worker->s->lbstatus += worker->s->lbfactor;
914                     total_factor += worker->s->lbfactor;
915                     if (!mycandidate || worker->s->lbstatus > mycandidate->s->lbstatus)
916                         mycandidate = worker;
917                 }
918             }
919             checked_standby = checking_standby++;
920         }
921         cur_lbset++;
922     } while (cur_lbset < max_lbset && !mycandidate);
923
924     if (mycandidate) {
925         mycandidate->s->lbstatus -= total_factor;
926         mycandidate->s->elected++;
927     }
928
929     return mycandidate;
930 }
931
932 /*
933  * The idea behind the find_best_bytraffic scheduler is the following:
934  *
935  * We know the amount of traffic (bytes in and out) handled by each
936  * worker. We normalize that traffic by each workers' weight. So assuming
937  * a setup as below:
938  *
939  * worker     a    b    c
940  * lbfactor   1    1    3
941  *
942  * the scheduler will allow worker c to handle 3 times the
943  * traffic of a and b. If each request/response results in the
944  * same amount of traffic, then c would be accessed 3 times as
945  * often as a or b. If, for example, a handled a request that
946  * resulted in a large i/o bytecount, then b and c would be
947  * chosen more often, to even things out.
948  */
949 static proxy_worker *find_best_bytraffic(proxy_balancer *balancer,
950                                          request_rec *r)
951 {
952     int i;
953     apr_off_t mytraffic = 0;
954     apr_off_t curmin = 0;
955     proxy_worker *worker;
956     int checking_standby = 0;
957     int checked_standby = 0;
958     proxy_worker *mycandidate = NULL;
959     int cur_lbset = 0;
960     int max_lbset = 0;
961
962     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
963                  "proxy: Entering bytraffic for BALANCER (%s)",
964                  balancer->name);
965
966     /* First try to see if we have available candidate */
967     do {
968         while (!mycandidate && !checked_standby) {
969             worker = (proxy_worker *)balancer->workers->elts;
970             for (i = 0; i < balancer->workers->nelts; i++, worker++) {
971                 if (!checking_standby) {    /* first time through */
972                     if (worker->s->lbset > max_lbset)
973                         max_lbset = worker->s->lbset;
974                 }
975                 if (worker->s->lbset > cur_lbset)
976                     continue;
977                 if ( (checking_standby ? !PROXY_WORKER_IS_STANDBY(worker) : PROXY_WORKER_IS_STANDBY(worker)) )
978                     continue;
979                 /* If the worker is in error state run
980                  * retry on that worker. It will be marked as
981                  * operational if the retry timeout is elapsed.
982                  * The worker might still be unusable, but we try
983                  * anyway.
984                  */
985                 if (!PROXY_WORKER_IS_USABLE(worker))
986                     ap_proxy_retry_worker("BALANCER", worker, r->server);
987                 /* Take into calculation only the workers that are
988                  * not in error state or not disabled.
989                  */
990                 if (PROXY_WORKER_IS_USABLE(worker)) {
991                     mytraffic = (worker->s->transferred/worker->s->lbfactor) +
992                                 (worker->s->read/worker->s->lbfactor);
993                     if (!mycandidate || mytraffic < curmin) {
994                         mycandidate = worker;
995                         curmin = mytraffic;
996                     }
997                 }
998             }
999             checked_standby = checking_standby++;
1000         }
1001         cur_lbset++;
1002     } while (cur_lbset < max_lbset && !mycandidate);
1003
1004     if (mycandidate) {
1005         mycandidate->s->elected++;
1006     }
1007
1008     return mycandidate;
1009 }
1010
1011 /*
1012  * How to add additional lbmethods:
1013  *   1. Create func which determines "best" candidate worker
1014  *      (eg: find_best_bytraffic, above)
1015  *   2. Register it as a provider.
1016  */
1017 static const proxy_balancer_method byrequests =
1018 {
1019     "byrequests",
1020     &find_best_byrequests,
1021     NULL
1022 };
1023
1024 static const proxy_balancer_method bytraffic =
1025 {
1026     "bytraffic",
1027     &find_best_bytraffic,
1028     NULL
1029 };
1030
1031 static void ap_proxy_balancer_register_hook(apr_pool_t *p)
1032 {
1033     /* Only the mpm_winnt has child init hook handler.
1034      * make sure that we are called after the mpm
1035      * initializes and after the mod_proxy
1036      */
1037     static const char *const aszPred[] = { "mpm_winnt.c", "mod_proxy.c", NULL};
1038      /* manager handler */
1039     ap_hook_handler(balancer_handler, NULL, NULL, APR_HOOK_FIRST);
1040     ap_hook_child_init(child_init, aszPred, NULL, APR_HOOK_MIDDLE);
1041     proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);
1042     proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);
1043     proxy_hook_canon_handler(proxy_balancer_canon, NULL, NULL, APR_HOOK_FIRST);
1044     ap_register_provider(p, PROXY_LBMETHOD, "bytraffic", "0", &bytraffic);
1045     ap_register_provider(p, PROXY_LBMETHOD, "byrequests", "0", &byrequests);
1046 }
1047
1048 module AP_MODULE_DECLARE_DATA proxy_balancer_module = {
1049     STANDARD20_MODULE_STUFF,
1050     NULL,       /* create per-directory config structure */
1051     NULL,       /* merge per-directory config structures */
1052     NULL,       /* create per-server config structure */
1053     NULL,       /* merge per-server config structures */
1054     NULL,       /* command apr_table_t */
1055     ap_proxy_balancer_register_hook /* register hooks */
1056 };