]> granicus.if.org Git - apache/blob - modules/proxy/proxy_balancer.c
Add session route to request rec notes if present.
[apache] / modules / proxy / proxy_balancer.c
1 /* Copyright 1999-2004 The Apache Software Foundation
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /* Load balancer module for Apache proxy */
17
18 #define CORE_PRIVATE
19
20 #include "mod_proxy.h"
21 #include "ap_mpm.h"
22 #include "apr_version.h"
23
24 module AP_MODULE_DECLARE_DATA proxy_balancer_module;
25
26 #if APR_HAS_THREADS
27 #define PROXY_BALANCER_LOCK(b)      apr_thread_mutex_lock((b)->mutex)
28 #define PROXY_BALANCER_UNLOCK(b)    apr_thread_mutex_unlock((b)->mutex)
29 #else
30 #define PROXY_BALANCER_LOCK(b)      APR_SUCCESS
31 #define PROXY_BALANCER_UNLOCK(b)    APR_SUCCESS
32 #endif
33
34
35 /* Retrieve the parameter with the given name                                */
36 static char *get_path_param(apr_pool_t *pool, char *url,
37                             const char *name)
38 {
39     char *path = NULL;
40     
41     for (path = strstr(url, name); path; path = strstr(path + 1, name)) {
42         path += (strlen(name) + 1);
43         if (*path == '=') {
44             /*
45              * Session path was found, get it's value
46              */
47             ++path;
48             if (strlen(path)) {
49                 char *q;
50                 path = apr_pstrdup(pool, path);
51                 if ((q = strchr(path, '?')))
52                     *q = '\0';
53                 return path;
54             }
55         }
56     }
57     return NULL;
58 }
59
60 static char *get_cookie_param(request_rec *r, const char *name)
61 {
62     const char *cookies;
63     const char *start_cookie;
64
65     if ((cookies = apr_table_get(r->headers_in, "Cookie"))) {
66         for (start_cookie = ap_strstr_c(cookies, name); start_cookie; 
67              start_cookie = ap_strstr_c(start_cookie + 1, name)) {
68             if (start_cookie == cookies ||
69                 start_cookie[-1] == ';' ||
70                 start_cookie[-1] == ',' ||
71                 isspace(start_cookie[-1])) {
72                 
73                 start_cookie += strlen(name);
74                 while(*start_cookie && isspace(*start_cookie))
75                     ++start_cookie;
76                 if (*start_cookie == '=' && start_cookie[1]) {
77                     /*
78                      * Session cookie was found, get it's value
79                      */
80                     char *end_cookie, *cookie;
81                     ++start_cookie;
82                     cookie = apr_pstrdup(r->pool, start_cookie);
83                     if ((end_cookie = strchr(cookie, ';')) != NULL)
84                         *end_cookie = '\0';
85                     if((end_cookie = strchr(cookie, ',')) != NULL)
86                         *end_cookie = '\0';
87                     return cookie;
88                 }
89             }
90         }     
91     }
92     return NULL;
93 }
94
95 static proxy_runtime_worker *find_route_worker(proxy_balancer *balancer,
96                                                const char *route)
97 {
98     int i;
99     proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts;
100     for (i = 0; i < balancer->workers->nelts; i++) {
101         if (worker->w->route && strcmp(worker->w->route, route) == 0) {
102             return worker;
103         }
104         worker++;
105     }
106     return NULL;
107 }
108
109 static proxy_runtime_worker *find_session_route(proxy_balancer *balancer,
110                                                 request_rec *r,
111                                                 char **route,
112                                                 char **url)
113 {
114     if (!balancer->sticky)
115         return NULL;
116     /* Try to find the sticky route inside url */
117     *route = get_path_param(r->pool, *url, balancer->sticky);
118     if (!*route)
119         *route = get_cookie_param(r, balancer->sticky);
120     if (*route) {
121         proxy_runtime_worker *worker =  find_route_worker(balancer, *route);
122         /* TODO: make worker status codes */
123         /* See if we have a redirection route */
124         if (worker && !PROXY_WORKER_IS_USABLE(worker->w)) {
125             if (worker->w->redirect)
126                 worker = find_route_worker(balancer, worker->w->redirect);
127             /* Check if the redirect worker is usable */
128             if (worker && !PROXY_WORKER_IS_USABLE(worker->w))
129                 worker = NULL;
130         }
131         else
132             worker = NULL;
133         return worker;
134     }
135     else
136         return NULL;
137 }
138
139 static proxy_runtime_worker *find_best_worker(proxy_balancer *balancer,
140                                               request_rec *r)
141 {
142     int i;
143     double total_factor = 0.0;
144     proxy_runtime_worker *worker = (proxy_runtime_worker *)balancer->workers->elts;
145     proxy_runtime_worker *candidate = NULL;
146
147     /* First try to see if we have available candidate */
148     for (i = 0; i < balancer->workers->nelts; i++) {
149         /* See if the retry timeout is ellapsed
150          * for the workers flagged as IN_ERROR
151          */
152         if (!PROXY_WORKER_IS_USABLE(worker->w))
153             ap_proxy_retry_worker("BALANCER", worker->w, r->server);
154         /* If the worker is not in error state
155          * or not disabled.
156          */
157         if (PROXY_WORKER_IS_USABLE(worker->w)) {
158             if (!candidate)
159                 candidate = worker;
160             else {
161                 /* See if the worker has a larger number of free channels */
162                 if (worker->w->cp->nfree > candidate->w->cp->nfree)
163                     candidate = worker;
164             }
165             /* Total factor should allways be 100.
166              * This is for cases when worker is in error state.
167              * It will force the even request distribution
168              */
169             total_factor += worker->s->lbfactor;
170         }
171         worker++;
172     }
173     if (!candidate) {
174         /* All the workers are in error state or disabled.
175          * If the balancer has a timeout wait.
176          */
177 #if APR_HAS_THREADS
178         if (balancer->timeout) {
179             /* XXX: This can perhaps be build using some 
180              * smarter mechanism, like tread_cond.
181              * But since the statuses can came from 
182              * different childs, use the provided algo. 
183              */
184             apr_interval_time_t timeout = balancer->timeout;
185             apr_interval_time_t step, tval = 0;
186             balancer->timeout = 0;
187             step = timeout / 100;
188             while (tval < timeout) {
189                 apr_sleep(step);
190                 /* Try again */
191                 if ((candidate = find_best_worker(balancer, r)))
192                     break;
193                 tval += step;
194             }
195             /* restore the timeout */
196             balancer->timeout = timeout;
197         }
198 #endif
199     }
200     else {
201         /* We have at least one candidate that is not in
202          * error state or disabled.
203          * Now calculate the appropriate one 
204          */
205         worker = (proxy_runtime_worker *)balancer->workers->elts;
206         for (i = 0; i < balancer->workers->nelts; i++) {
207             /* If the worker is not error state
208              * or not in disabled mode
209              */
210             if (PROXY_WORKER_IS_USABLE(worker->w)) {
211                 /* 1. Find the worker with higher lbstatus.
212                  * Lbstatus is of higher importance then
213                  * the number of empty slots.
214                  */
215                 if (worker->s->lbstatus > candidate->s->lbstatus) {
216                     candidate = worker;
217                 }
218             }
219             worker++;
220         }
221         worker = (proxy_runtime_worker *)balancer->workers->elts;
222         for (i = 0; i < balancer->workers->nelts; i++) {
223             /* If the worker is not error state
224              * or not in disabled mode
225              */
226             if (PROXY_WORKER_IS_USABLE(worker->w)) {
227                 /* XXX: The lbfactor can be update using bytes transfered
228                  * Right now, use the round-robin scheme
229                  */
230                 worker->s->lbstatus += worker->s->lbfactor;
231                 if (worker->s->lbstatus >= total_factor)
232                     worker->s->lbstatus = worker->s->lbfactor;
233             }
234             worker++;
235         }
236     }
237     return candidate;
238 }
239
240 static int rewrite_url(request_rec *r, proxy_worker *worker,
241                         char **url)
242 {
243     const char *scheme = strstr(*url, "://");
244     const char *path = NULL;
245     
246     if (scheme)
247         path = ap_strchr_c(scheme + 3, '/');
248
249     /* we break the URL into host, port, uri */
250     if (!worker) {
251         return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
252                              "missing worker. URI cannot be parsed: ", *url,
253                              NULL));
254     }
255
256     *url = apr_pstrcat(r->pool, worker->name, path, NULL);
257    
258     return OK;
259 }
260
261 static int proxy_balancer_pre_request(proxy_worker **worker,
262                                       proxy_balancer **balancer,
263                                       request_rec *r,
264                                       proxy_server_conf *conf, char **url)
265 {
266     int access_status;
267     proxy_runtime_worker *runtime;
268     char *route;
269     apr_status_t rv;
270
271     *worker = NULL;
272     /* Spet 1: check if the url is for us */
273     if (!(*balancer = ap_proxy_get_balancer(r->pool, conf, *url)))
274         return DECLINED;
275     
276     /* Step 2: find the session route */
277     
278     runtime = find_session_route(*balancer, r, &route, url);
279     if (!runtime) {
280         if (route && (*balancer)->sticky_force) {
281             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
282                          "proxy: BALANCER: (%s). All workers are in error state for route (%s)",
283                          (*balancer)->name, route);
284             return HTTP_SERVICE_UNAVAILABLE;
285         }
286     }
287     else {
288         int i;
289         proxy_runtime_worker *workers;
290         /* We have a sticky load balancer */
291         *worker = runtime->w;
292         /* Update the workers status 
293          * so that even session routes get
294          * into account.
295          */
296         workers = (proxy_runtime_worker *)(*balancer)->workers->elts;
297         for (i = 0; i < (*balancer)->workers->nelts; i++) {
298             /* For now assume that all workers are OK */
299             workers->s->lbstatus += workers->s->lbfactor;
300             if (workers->s->lbstatus >= 100.0)
301                 workers->s->lbstatus = workers->s->lbfactor;
302             workers++;
303         }
304     }
305     /* Lock the LoadBalancer
306      * XXX: perhaps we need the process lock here
307      */
308     if ((rv = PROXY_BALANCER_LOCK(*balancer)) != APR_SUCCESS) {
309         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
310                      "proxy: BALANCER: lock");
311         return DECLINED;
312     }
313     if (!*worker) {
314         runtime = find_best_worker(*balancer, r);
315         if (!runtime) {
316             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
317                          "proxy: BALANCER: (%s). All workers are in error state",
318                          (*balancer)->name);
319         
320             PROXY_BALANCER_UNLOCK(*balancer);
321             return HTTP_SERVICE_UNAVAILABLE;
322         }
323         *worker = runtime->w;
324     }
325     /* Decrease the free channels number */
326     if ((*worker)->cp->nfree)
327         --(*worker)->cp->nfree;
328
329     PROXY_BALANCER_UNLOCK(*balancer);
330     
331     access_status = rewrite_url(r, *worker, url);
332     /* Add the session route to request notes if present */
333     if (route) {
334         apr_table_setn(r->notes, "session-sticky", (*balancer)->sticky);
335         apr_table_setn(r->notes, "session-route", route);
336     }
337     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
338                  "proxy_balancer_pre_request rewriting to %s", *url);
339     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
340                  "proxy_balancer_pre_request worker (%s) free %d",
341                  (*worker)->name,
342                  (*worker)->cp->nfree);
343
344     return access_status;
345
346
347 static int proxy_balancer_post_request(proxy_worker *worker,
348                                        proxy_balancer *balancer,
349                                        request_rec *r,
350                                        proxy_server_conf *conf)
351 {
352     int access_status;
353     if (!balancer)
354         access_status = DECLINED;
355     else { 
356         apr_status_t rv;
357         if ((rv = PROXY_BALANCER_LOCK(balancer)) != APR_SUCCESS) {
358             ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
359                          "proxy: BALANCER: lock");
360             return HTTP_INTERNAL_SERVER_ERROR;
361         }
362         /* increase the free channels number */
363         if (worker->cp->nfree)
364             worker->cp->nfree++;
365         /* TODO: calculate the bytes transfered */
366
367         /* TODO: update the scoreboard status */
368
369         PROXY_BALANCER_UNLOCK(balancer);        
370         access_status = OK;
371     }
372     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
373              "proxy_balancer_post_request for (%s)", balancer->name);
374
375     return access_status;
376
377
378 static void ap_proxy_balancer_register_hook(apr_pool_t *p)
379 {
380     proxy_hook_pre_request(proxy_balancer_pre_request, NULL, NULL, APR_HOOK_FIRST);    
381     proxy_hook_post_request(proxy_balancer_post_request, NULL, NULL, APR_HOOK_FIRST);    
382 }
383
384 module AP_MODULE_DECLARE_DATA proxy_balancer_module = {
385     STANDARD20_MODULE_STUFF,
386     NULL,       /* create per-directory config structure */
387     NULL,       /* merge per-directory config structures */
388     NULL,       /* create per-server config structure */
389     NULL,       /* merge per-server config structures */
390     NULL,       /* command apr_table_t */
391     ap_proxy_balancer_register_hook /* register hooks */
392 };