On Wed, Jul 8, 2015 at 11:11 PM, Poul-Henning Kamp <[email protected]> wrote: > -------- > In message > <CABoVN9CP=yzv73d5vhu-5racuufsy1ad18m-zqamhkjakvb...@mail.gmail.com>, Dridi > Boukelmoune writes: > >>>>Subject: [PATCH 2/5] Keep track of backends references and connections >>> >>> I've done this entirely differently. >>> >>> Fixes #1755 >> >>Please revisit this one, my patch also fixes two panics I just got while >>rebasing against the current trunk. See attached test logs. > > I tried releaseing the lock as we talked about on IRC and that solves > the problem. Committed.
Ack. However, I see a flaw in the VBE_Poll implementation. If you have deleted backends with a zero n_conn, but the first one is still being used, nothing's deleted. It makes it less predictable than my (convoluted) delete-when-deletable approach. It doesn't hurt the test suite, which *should be* predictable anyway. And I have updated the test suite to trigger a VBE_Poll before assertions on the n_backend counter. >>>> Subject: [PATCH 5/5] Add more information to a backend panic message >>> >>> This doesn't work with the way I did #2/5 above. >> >>Do I send a patch or simply commit? > > Just commit it. Done. I have attached a new patch set which would have been ready before my rehearsal if it weren't for a last-minute phone call (let's call the culprit Dave). I have rearranged the patches against the current master hoping that they'll be reviewed this week. The first patch is my initial fix for 1755 and dynamic-backends-related issues, reworked after the recent changes in master (probe "deleted" and VBE_Poll). I rely on refcount to avoid dangling struct backend pointers, which can now happen with dynamic backends. The second patch is the actual test suite. The last two bonus patches are a first stab at a DNS director for 4.1, but requests fail with IPv6 backends. Cheers, Dridi
From 6b2654bdf577bbc38e875d546356faaea6fdbf2b Mon Sep 17 00:00:00 2001 From: Dridi Boukelmoune <[email protected]> Date: Mon, 6 Jul 2015 08:19:43 +0200 Subject: [PATCH 1/5] Keep track of backends references and connections --- bin/varnishd/cache/cache_backend.c | 24 +++++++++++++++++++----- bin/varnishd/cache/cache_backend.h | 2 +- bin/varnishd/cache/cache_backend_cfg.c | 13 +++++++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/bin/varnishd/cache/cache_backend.c b/bin/varnishd/cache/cache_backend.c index 611c089..11aae0d 100644 --- a/bin/varnishd/cache/cache_backend.c +++ b/bin/varnishd/cache/cache_backend.c @@ -71,12 +71,17 @@ vbe_dir_getfd(struct worker *wrk, struct backend *bp, struct busyobj *bo) CHECK_OBJ_NOTNULL(bp, BACKEND_MAGIC); AN(bp->vsc); + Lck_Lock(&bp->mtx); + bp->refcount++; + Lck_Unlock(&bp->mtx); + if (!VBE_Healthy(bp, NULL)) { // XXX: per backend stats ? VSC_C_main->backend_unhealthy++; return (NULL); } + assert(bp->n_conn >= 0); if (bp->max_connections > 0 && bp->n_conn >= bp->max_connections) { // XXX: per backend stats ? VSC_C_main->backend_busy++; @@ -103,7 +108,6 @@ vbe_dir_getfd(struct worker *wrk, struct backend *bp, struct busyobj *bo) AN(vc->addr); Lck_Lock(&bp->mtx); - bp->refcount++; bp->n_conn++; bp->vsc->conn++; Lck_Unlock(&bp->mtx); @@ -165,12 +169,15 @@ vbe_dir_finish(const struct director *d, struct worker *wrk, VSC_C_main->backend_recycle++; VBT_Recycle(wrk, bp->tcp_pool, &vbc); } - assert(bp->n_conn > 0); - bp->n_conn--; #define ACCT(foo) bp->vsc->foo += bo->acct.foo; #include "tbl/acct_fields_bereq.h" #undef ACCT + + assert(bp->n_conn > 0); + bp->n_conn--; + bp->refcount--; Lck_Unlock(&bp->mtx); + bo->htc = NULL; } @@ -199,7 +206,10 @@ vbe_dir_gethdrs(const struct director *d, struct worker *wrk, vbc = vbe_dir_getfd(wrk, bp, bo); if (vbc == NULL) { VSLb(bo->vsl, SLT_FetchError, "no backend connection"); - return (-1); + Lck_Lock(&bp->mtx); + bp->refcount--; + Lck_Unlock(&bp->mtx); + break; } AN(bo->htc); if (vbc->state != VBC_STATE_STOLEN) @@ -295,6 +305,10 @@ vbe_dir_http1pipe(const struct director *d, struct req *req, struct busyobj *bo) if (vbc == NULL) { VSLb(bo->vsl, SLT_FetchError, "no backend connection"); SES_Close(req->sp, SC_RX_TIMEOUT); + V1P_Charge(req, &v1a, bp->vsc); + Lck_Lock(&bp->mtx); + bp->refcount--; + Lck_Unlock(&bp->mtx); } else { i = V1F_SendReq(req->wrk, bo, &v1a.bereq, 1); VSLb_ts_req(req, "Pipe", W_TIM_real(req->wrk)); @@ -305,9 +319,9 @@ vbe_dir_http1pipe(const struct director *d, struct req *req, struct busyobj *bo) VSLb_ts_req(req, "PipeSess", W_TIM_real(req->wrk)); SES_Close(req->sp, SC_TX_PIPE); bo->htc->doclose = SC_TX_PIPE; + V1P_Charge(req, &v1a, bp->vsc); vbe_dir_finish(d, req->wrk, bo); } - V1P_Charge(req, &v1a, bp->vsc); } /*--------------------------------------------------------------------*/ diff --git a/bin/varnishd/cache/cache_backend.h b/bin/varnishd/cache/cache_backend.h index 97e70c1..e5af949 100644 --- a/bin/varnishd/cache/cache_backend.h +++ b/bin/varnishd/cache/cache_backend.h @@ -107,7 +107,7 @@ unsigned VBE_Healthy(const struct backend *b, double *changed); #ifdef VCL_MET_MAX void VBE_Event(struct backend *, enum vcl_event_e); #endif -void VBE_Delete(struct backend *be); +void VBE_Delete(struct backend *); /* cache_backend_probe.c */ void VBP_Insert(struct backend *b, struct vrt_backend_probe const *p, diff --git a/bin/varnishd/cache/cache_backend_cfg.c b/bin/varnishd/cache/cache_backend_cfg.c index a9199d3..0d54495 100644 --- a/bin/varnishd/cache/cache_backend_cfg.c +++ b/bin/varnishd/cache/cache_backend_cfg.c @@ -140,10 +140,11 @@ VRT_delete_backend(VRT_CTX, struct director **dp) *dp = NULL; CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC); CAST_OBJ_NOTNULL(be, d->priv, BACKEND_MAGIC); + Lck_Lock(&be->mtx); be->admin_health = vbe_ah_deleted; be->health_changed = VTIM_real(); - r = be->n_conn; + r = be->refcount; if (r > 0) { /* move to front of list for fast access */ VTAILQ_REMOVE(&backends, be, list); @@ -189,9 +190,17 @@ VBE_Delete(struct backend *be) { CHECK_OBJ_NOTNULL(be, BACKEND_MAGIC); + if (be->vsc) { + /* deleted dynamic backend is not referenced anymore */ + VBE_Event(be, VCL_EVENT_COLD); + } + if (be->probe != NULL) VBP_Remove(be); + AZ(be->refcount); + AZ(be->n_conn); + Lck_Lock(&backends_mtx); VTAILQ_REMOVE(&backends, be, list); VSC_C_main->n_backend--; @@ -434,7 +443,7 @@ VBE_Poll(void) break; if (be->admin_health != vbe_ah_deleted) break; - if (be->n_conn > 0) + if (be->refcount > 0) break; Lck_Unlock(&backends_mtx); VCL_DelBackend(be); -- 2.1.0
From d97b1e6db82448f145a9b7527502fe69ea03554f Mon Sep 17 00:00:00 2001 From: Dridi Boukelmoune <[email protected]> Date: Mon, 6 Jul 2015 08:31:06 +0200 Subject: [PATCH 2/5] Add a test suite for dynamic backends --- bin/varnishtest/tests/d00007.vtc | 28 +++++ bin/varnishtest/tests/d00008.vtc | 52 ++++++++++ bin/varnishtest/tests/d00009.vtc | 58 +++++++++++ bin/varnishtest/tests/d00010.vtc | 59 +++++++++++ bin/varnishtest/tests/d00011.vtc | 56 ++++++++++ bin/varnishtest/tests/d00012.vtc | 77 ++++++++++++++ bin/varnishtest/tests/d00013.vtc | 53 ++++++++++ lib/libvmod_debug/Makefile.am | 5 +- lib/libvmod_debug/vmod.vcc | 12 +++ lib/libvmod_debug/vmod_debug_dyn.c | 206 +++++++++++++++++++++++++++++++++++++ 10 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 bin/varnishtest/tests/d00007.vtc create mode 100644 bin/varnishtest/tests/d00008.vtc create mode 100644 bin/varnishtest/tests/d00009.vtc create mode 100644 bin/varnishtest/tests/d00010.vtc create mode 100644 bin/varnishtest/tests/d00011.vtc create mode 100644 bin/varnishtest/tests/d00012.vtc create mode 100644 bin/varnishtest/tests/d00013.vtc create mode 100644 lib/libvmod_debug/vmod_debug_dyn.c diff --git a/bin/varnishtest/tests/d00007.vtc b/bin/varnishtest/tests/d00007.vtc new file mode 100644 index 0000000..298fbf7 --- /dev/null +++ b/bin/varnishtest/tests/d00007.vtc @@ -0,0 +1,28 @@ +varnishtest "Test dynamic backends" + +server s1 { + rxreq + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + set req.backend_hint = s1.backend(); + } +} -start + +varnish v1 -expect MAIN.n_backend == 2 + +client c1 { + txreq + rxresp + expect resp.status == 200 +} -run diff --git a/bin/varnishtest/tests/d00008.vtc b/bin/varnishtest/tests/d00008.vtc new file mode 100644 index 0000000..2427185 --- /dev/null +++ b/bin/varnishtest/tests/d00008.vtc @@ -0,0 +1,52 @@ +varnishtest "Test dynamic backends hot swap" + +server s1 { + rxreq + expect req.url == "/foo" + txresp +} -start + +server s2 { + rxreq + expect req.url == "/bar" + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + if (req.method == "SWAP") { + s1.hot_swap(req.http.X-Addr ,req.http.X-Port); + return (synth(200)); + } + set req.backend_hint = s1.backend(); + } +} -start + +varnish v1 -expect MAIN.n_backend == 2 + +client c1 { + txreq -url "/foo" + rxresp + expect resp.status == 200 + + txreq -req "SWAP" -hdr "X-Addr: ${s2_addr}" -hdr "X-Port: ${s2_port}" + rxresp + expect resp.status == 200 + + txreq -url "/bar" + rxresp + expect resp.status == 200 +} -run + +delay 1 + +varnish v1 -cli backend.list +varnish v1 -expect MAIN.n_backend == 2 diff --git a/bin/varnishtest/tests/d00009.vtc b/bin/varnishtest/tests/d00009.vtc new file mode 100644 index 0000000..f257d75 --- /dev/null +++ b/bin/varnishtest/tests/d00009.vtc @@ -0,0 +1,58 @@ +varnishtest "Test dynamic backends hot swap while being used" + +server s1 { + rxreq + expect req.url == "/foo" + sema r1 sync 2 + sema r2 sync 2 + txresp +} -start + +server s2 { + rxreq + expect req.url == "/bar" + sema r2 sync 2 + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + if (req.method == "SWAP") { + s1.hot_swap(req.http.X-Addr ,req.http.X-Port); + return (synth(200)); + } + set req.backend_hint = s1.backend(); + } +} -start + +varnish v1 -expect MAIN.n_backend == 2 + +client c1 { + txreq -url "/foo" + rxresp + expect resp.status == 200 +} -start + +client c2 { + sema r1 sync 2 + txreq -req "SWAP" -hdr "X-Addr: ${s2_addr}" -hdr "X-Port: ${s2_port}" + rxresp + expect resp.status == 200 + + txreq -url "/bar" + rxresp + expect resp.status == 200 +} -run + +client c1 -wait + +varnish v1 -cli backend.list +varnish v1 -expect MAIN.n_backend == 2 diff --git a/bin/varnishtest/tests/d00010.vtc b/bin/varnishtest/tests/d00010.vtc new file mode 100644 index 0000000..77b2221 --- /dev/null +++ b/bin/varnishtest/tests/d00010.vtc @@ -0,0 +1,59 @@ +varnishtest "Test dynamic backends hot swap during a pipe" + +server s1 { + rxreq + expect req.url == "/foo" + sema r1 sync 2 + sema r2 sync 2 + txresp +} -start + +server s2 { + rxreq + expect req.url == "/bar" + sema r2 sync 2 + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + if (req.method == "SWAP") { + s1.hot_swap(req.http.X-Addr ,req.http.X-Port); + return (synth(200)); + } + set req.backend_hint = s1.backend(); + return (pipe); + } +} -start + +varnish v1 -expect MAIN.n_backend == 2 + +client c1 { + txreq -url "/foo" + rxresp + expect resp.status == 200 +} -start + +client c2 { + sema r1 sync 2 + txreq -req "SWAP" -hdr "X-Addr: ${s2_addr}" -hdr "X-Port: ${s2_port}" + rxresp + expect resp.status == 200 + + txreq -url "/bar" + rxresp + expect resp.status == 200 +} -run + +client c1 -wait + +varnish v1 -cli backend.list +varnish v1 -expect MAIN.n_backend == 2 diff --git a/bin/varnishtest/tests/d00011.vtc b/bin/varnishtest/tests/d00011.vtc new file mode 100644 index 0000000..1af7e5e --- /dev/null +++ b/bin/varnishtest/tests/d00011.vtc @@ -0,0 +1,56 @@ +varnishtest "Test a dynamic backend hot swap after it was picked by a bereq" + +server s1 { +} -start + +server s2 { + rxreq + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + if (req.method == "SWAP") { + s1.hot_swap(req.http.X-Addr ,req.http.X-Port); + return (synth(200)); + } + } + + sub vcl_backend_fetch { + set bereq.backend = s1.backend(); + # hot swap should happen while we sleep + debug.sleep(2s); + } +} -start + +varnish v1 -expect MAIN.n_backend == 2 + +client c1 { + txreq + sema r2 sync 2 + rxresp + expect resp.status == 200 +} + +client c2 { + sema r2 sync 2 + delay 0.1 + txreq -req "SWAP" -hdr "X-Addr: ${s2_addr}" -hdr "X-Port: ${s2_port}" + rxresp + expect resp.status == 200 +} + +client c1 -start +client c2 -run +client c1 -wait + +varnish v1 -cli backend.list +varnish v1 -expect MAIN.n_backend == 2 diff --git a/bin/varnishtest/tests/d00012.vtc b/bin/varnishtest/tests/d00012.vtc new file mode 100644 index 0000000..fc5c783 --- /dev/null +++ b/bin/varnishtest/tests/d00012.vtc @@ -0,0 +1,77 @@ +varnishtest "Test a dynamic backend discard during a request" + +# vcl.discard testing inspired by v00006.vtc from commit e1f7207 + +server s1 { + rxreq + expect req.url == "/foo" + sema r1 sync 2 + txresp +} -start + +varnish v1 -arg "-p thread_pools=1" -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + set req.backend_hint = s1.backend(); + } +} -start + +client c1 { + txreq -url "/foo" + rxresp + expect resp.status == 200 +} -start + +varnish v1 -expect MAIN.n_backend == 2 +# expected: vcl1.dummy, vcl1.s1 + +server s2 { + rxreq + expect req.url == "/bar" + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s2 = debug.dyn("${s2_addr}", "${s2_port}"); + } + + sub vcl_recv { + set req.backend_hint = s2.backend(); + } +} + +varnish v1 -cli "vcl.discard vcl1" +sema r1 sync 2 + +client c1 -wait +delay 2 + +varnish v1 -expect MAIN.n_backend == 4 +# expected: vcl1.dummy, vcl1.s1, vcl2.dummy, vcl2.s2 + +varnish v1 -expect n_vcl_avail == 1 +varnish v1 -expect n_vcl_discard == 1 + +client c1 { + txreq -url "/bar" + rxresp + expect resp.status == 200 +} -run + +varnish v1 -cli "vcl.list" +varnish v1 -expect MAIN.n_backend == 2 +# expected: vcl2.dummy, vcl2.s2 +varnish v1 -expect n_vcl_avail == 1 +varnish v1 -expect n_vcl_discard == 0 diff --git a/bin/varnishtest/tests/d00013.vtc b/bin/varnishtest/tests/d00013.vtc new file mode 100644 index 0000000..d620e27 --- /dev/null +++ b/bin/varnishtest/tests/d00013.vtc @@ -0,0 +1,53 @@ +varnishtest "Test a dynamic backend hot swap after it was hinted to a req" + +server s1 { +} -start + +server s2 { + rxreq + txresp +} -start + +varnish v1 -vcl { + import ${vmod_debug}; + + backend dummy { .host = "${bad_ip}"; } + + sub vcl_init { + new s1 = debug.dyn("${s1_addr}", "${s1_port}"); + } + + sub vcl_recv { + if (req.method == "SWAP") { + s1.hot_swap(req.http.X-Addr ,req.http.X-Port); + return (synth(200)); + } + set req.backend_hint = s1.backend(); + # hot swap should happen while we sleep + debug.sleep(2s); + } +} -start + +varnish v1 -expect MAIN.n_backend == 2 + +client c1 { + txreq + sema r2 sync 2 + rxresp + expect resp.status == 200 +} + +client c2 { + sema r2 sync 2 + delay 0.1 + txreq -req "SWAP" -hdr "X-Addr: ${s2_addr}" -hdr "X-Port: ${s2_port}" + rxresp + expect resp.status == 200 +} + +client c1 -start +client c2 -run +client c1 -wait + +varnish v1 -cli backend.list +varnish v1 -expect MAIN.n_backend == 2 diff --git a/lib/libvmod_debug/Makefile.am b/lib/libvmod_debug/Makefile.am index d189102..9115713 100644 --- a/lib/libvmod_debug/Makefile.am +++ b/lib/libvmod_debug/Makefile.am @@ -17,14 +17,15 @@ libvmod_debug_la_LDFLAGS = $(AM_LDFLAGS) -module -export-dynamic -avoid-version libvmod_debug_la_SOURCES = \ vmod_debug.c \ - vmod_debug_obj.c + vmod_debug_obj.c \ + vmod_debug_dyn.c nodist_libvmod_debug_la_SOURCES = \ vcc_if.c \ vcc_if.h # BUILT_SOURCES is only a hack and dependency tracking does not help for the first build -vmod_debug.lo vmod_debug_obj.lo: vcc_if.h +vmod_debug.lo vmod_debug_obj.lo vmod_debug_dyn.lo: vcc_if.h vcc_if.c vcc_if.h vmod_debug.rst vmod_debug.man.rst: $(vmodtool) $(vmod_srcdir)/vmod.vcc @PYTHON@ $(vmodtool) $(vmodtoolargs) $(vmod_srcdir)/vmod.vcc diff --git a/lib/libvmod_debug/vmod.vcc b/lib/libvmod_debug/vmod.vcc index 305deaa..464b223 100644 --- a/lib/libvmod_debug/vmod.vcc +++ b/lib/libvmod_debug/vmod.vcc @@ -109,3 +109,15 @@ Function to fail vcl_init{} $Function VOID sleep(DURATION) Block the current worker thread. + +$Object dyn(STRING addr, STRING port) + +Dynamically create a single-backend director + +$Method BACKEND .backend() + +Return the dynamic backend. + +$Method VOID .hot_swap(STRING addr, STRING port) + +Dynamically replace the backend by a new one. diff --git a/lib/libvmod_debug/vmod_debug_dyn.c b/lib/libvmod_debug/vmod_debug_dyn.c new file mode 100644 index 0000000..7fe7ce1 --- /dev/null +++ b/lib/libvmod_debug/vmod_debug_dyn.c @@ -0,0 +1,206 @@ +/*- + * Copyright (c) 2015 Varnish Software AS + * All rights reserved. + * + * Author: Dridi Boukelmoune <[email protected]> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" + +#include <netdb.h> +#include <pthread.h> +#include <stdlib.h> + +#include <sys/socket.h> +#include <sys/types.h> + +#include "vcl.h" +#include "vrt.h" + +#include "cache/cache.h" +#include "cache/cache_director.h" +#include "cache/cache_backend.h" + +#include "vsa.h" +#include "vcc_if.h" + +struct vmod_debug_dyn { + unsigned magic; +#define VMOD_DEBUG_DYN_MAGIC 0x9b77ccbd + pthread_mutex_t mtx; + char *vcl_name; + struct director *dir; + struct director dyn_dir; +}; + +static unsigned __match_proto__(vdi_healthy_f) +dyn_dir_healthy(const struct director *d, const struct busyobj *bo, + double *changed) +{ + struct vmod_debug_dyn *dyn; + unsigned healthy; + + CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC); + CAST_OBJ_NOTNULL(dyn, d->priv, VMOD_DEBUG_DYN_MAGIC); + + AZ(pthread_mutex_lock(&dyn->mtx)); + + AN(dyn->dir); + healthy = dyn->dir->healthy(dyn->dir, bo, changed); + + AZ(pthread_mutex_unlock(&dyn->mtx)); + + return (healthy); +} + +static const struct director * __match_proto__(vdi_resolve_f) +dyn_dir_resolve(const struct director *d, struct worker *wrk, + struct busyobj *bo) +{ + struct vmod_debug_dyn *dyn; + struct director *simple; + + CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC); + CAST_OBJ_NOTNULL(dyn, d->priv, VMOD_DEBUG_DYN_MAGIC); + + AZ(pthread_mutex_lock(&dyn->mtx)); + + AN(dyn->dir); + simple = dyn->dir; + + AZ(pthread_mutex_unlock(&dyn->mtx)); + + return (simple); +} + +static void +dyn_dir_init(VRT_CTX, struct vmod_debug_dyn *dyn, + VCL_STRING addr, VCL_STRING port) +{ + struct addrinfo hints, *res = NULL; + struct suckaddr *sa; + struct director *dir; + struct vrt_backend vrt; + + CHECK_OBJ_NOTNULL(dyn, VMOD_DEBUG_DYN_MAGIC); + AN(addr); + AN(port); + + INIT_OBJ(&vrt, VRT_BACKEND_MAGIC); + vrt.ipv4_addr = addr; + vrt.port = port; + vrt.vcl_name = dyn->vcl_name; + vrt.hosthdr = vrt.ipv4_addr; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + AZ(getaddrinfo(vrt.ipv4_addr, vrt.port, &hints, &res)); + AZ(res->ai_next); + + sa = malloc(vsa_suckaddr_len); + AN(sa); + AN(VSA_Build(sa, res->ai_addr, res->ai_addrlen)); + vrt.ipv4_suckaddr = sa; + + freeaddrinfo(res); + + dir = VRT_new_backend(ctx, &vrt); + AN(dir); + + AZ(pthread_mutex_lock(&dyn->mtx)); + if (dir != NULL && dyn->dir != NULL) + VRT_delete_backend(ctx, &dyn->dir); + + if (dir != NULL) { + AZ(dyn->dir); + dyn->dir = dir; + } + AZ(pthread_mutex_unlock(&dyn->mtx)); + + free(sa); +} + +VCL_VOID +vmod_dyn__init(VRT_CTX, struct vmod_debug_dyn **dynp, + const char *vcl_name, VCL_STRING addr, VCL_STRING port) +{ + struct vmod_debug_dyn *dyn; + + ASSERT_CLI(); + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + AN(dynp); + AZ(*dynp); + AN(vcl_name); + + ALLOC_OBJ(dyn, VMOD_DEBUG_DYN_MAGIC); + AN(dyn); + REPLACE(dyn->vcl_name, vcl_name); + + INIT_OBJ(&dyn->dyn_dir, DIRECTOR_MAGIC); + dyn->dyn_dir.name = "debug.dyn"; + dyn->dyn_dir.vcl_name = dyn->vcl_name; + dyn->dyn_dir.healthy = &dyn_dir_healthy; + dyn->dyn_dir.resolve = &dyn_dir_resolve; + dyn->dyn_dir.priv = dyn; + + AZ(pthread_mutex_init(&dyn->mtx, NULL)); + + dyn_dir_init(ctx, dyn, addr, port); + if (dyn->dir) + *dynp = dyn; +} + +VCL_VOID +vmod_dyn__fini(struct vmod_debug_dyn **dynp) +{ + struct vmod_debug_dyn *dyn; + + AN(dynp); + CAST_OBJ_NOTNULL(dyn, *dynp, VMOD_DEBUG_DYN_MAGIC); + /* at this point all backends will be deleted by the vcl */ + free(dyn->vcl_name); + AZ(pthread_mutex_destroy(&dyn->mtx)); + FREE_OBJ(dyn); + *dynp = NULL; +} + +VCL_BACKEND __match_proto__() +vmod_dyn_backend(VRT_CTX, struct vmod_debug_dyn *dyn) +{ + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(dyn, VMOD_DEBUG_DYN_MAGIC); + assert(dyn->dyn_dir.priv == dyn); + AN(dyn->dir); + return (&dyn->dyn_dir); +} + +VCL_VOID +vmod_dyn_hot_swap(VRT_CTX, struct vmod_debug_dyn *dyn, + VCL_STRING addr, VCL_STRING port) +{ + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(dyn, VMOD_DEBUG_DYN_MAGIC); + dyn_dir_init(ctx, dyn, addr, port); +} -- 2.1.0
From 14738c029ad1129493c07d12d525b0d7e0fc0112 Mon Sep 17 00:00:00 2001 From: Dridi Boukelmoune <[email protected]> Date: Tue, 7 Jul 2015 22:31:50 +0200 Subject: [PATCH 3/5] Resurrect VSA_Len Partial revert of 0848a1fe471c40a49e77df941c8e2cc133170ff7. --- include/vsa.h | 1 + lib/libvarnish/vsa.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/vsa.h b/include/vsa.h index 185be03..996afde 100644 --- a/include/vsa.h +++ b/include/vsa.h @@ -34,6 +34,7 @@ struct suckaddr; extern const int vsa_suckaddr_len; int VSA_Sane(const struct suckaddr *); +socklen_t VSA_Len(const struct suckaddr *); unsigned VSA_Port(const struct suckaddr *); int VSA_Compare(const struct suckaddr *, const struct suckaddr *); struct suckaddr *VSA_Clone(const struct suckaddr *sua); diff --git a/lib/libvarnish/vsa.c b/lib/libvarnish/vsa.c index 43d9a86..fc25cdd 100644 --- a/lib/libvarnish/vsa.c +++ b/lib/libvarnish/vsa.c @@ -306,6 +306,21 @@ VSA_Sane(const struct suckaddr *sua) } } +socklen_t +VSA_Len(const struct suckaddr *sua) +{ + CHECK_OBJ_NOTNULL(sua, SUCKADDR_MAGIC); + + switch(sua->sa.sa_family) { + case PF_INET: + return (sizeof(sua->sa4)); + case PF_INET6: + return (sizeof(sua->sa6)); + default: + return (0); + } +} + int VSA_Compare(const struct suckaddr *sua1, const struct suckaddr *sua2) { -- 2.1.0
From 5224cfb8a96f4a25ea6f3861a8b341b6fadc5588 Mon Sep 17 00:00:00 2001 From: Dridi Boukelmoune <[email protected]> Date: Wed, 8 Jul 2015 19:49:14 +0200 Subject: [PATCH 4/5] Resurrect the DNS director It doesn't seem IPv6-ready, always yielding a 503 error. --- lib/libvmod_directors/Makefile.am | 3 +- lib/libvmod_directors/dns.c | 384 ++++++++++++++++++++++++++++++++++++++ lib/libvmod_directors/vmod.vcc | 43 +++++ 3 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 lib/libvmod_directors/dns.c diff --git a/lib/libvmod_directors/Makefile.am b/lib/libvmod_directors/Makefile.am index 64b72a8..bc0261f 100644 --- a/lib/libvmod_directors/Makefile.am +++ b/lib/libvmod_directors/Makefile.am @@ -17,6 +17,7 @@ libvmod_directors_la_LDFLAGS = $(AM_LDFLAGS) -module -export-dynamic -avoid-vers libvmod_directors_la_SOURCES = \ vdir.c \ vdir.h \ + dns.c \ fall_back.c \ hash.c \ random.c \ @@ -27,7 +28,7 @@ nodist_libvmod_directors_la_SOURCES = \ vcc_if.h # BUILT_SOURCES is only a hack and dependency tracking does not help for the first build -vdir.lo fall_back.lo hash.lo random.lo round_robin.lo: vcc_if.h +vdir.lo dns.lo fall_back.lo hash.lo random.lo round_robin.lo: vcc_if.h vcc_if.c vcc_if.h vmod_directors.rst vmod_directors.man.rst: $(vmodtool) $(vmod_srcdir)/vmod.vcc @PYTHON@ $(vmodtool) $(vmodtoolargs) $(vmod_srcdir)/vmod.vcc diff --git a/lib/libvmod_directors/dns.c b/lib/libvmod_directors/dns.c new file mode 100644 index 0000000..b4f21a5 --- /dev/null +++ b/lib/libvmod_directors/dns.c @@ -0,0 +1,384 @@ +/*- + * Copyright (c) 2013 Varnish Software AS + * All rights reserved. + * + * Author: Dridi Boukelmoune <[email protected]> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" + +#include <arpa/inet.h> + +#include <sys/socket.h> +#include <sys/types.h> + +#include <errno.h> +#include <netdb.h> +#include <pthread.h> +#include <stdlib.h> + +#include "vrt.h" +#include "vcl.h" + +#include "cache/cache.h" +#include "cache/cache_director.h" +#include "cache/cache_backend.h" + +#include "vsa.h" +#include "vtim.h" +#include "vcc_if.h" + +struct vmod_directors_dns; + +struct dns_director { + struct vmod_directors_dns *dns; + struct vrt_backend *vrt; + struct director *simple_dir; + VTAILQ_ENTRY(dns_director) list; + char ip_addr[64]; + unsigned mark; +}; + +struct vmod_directors_dns { + unsigned magic; +#define VMOD_DIRECTORS_DNS_MAGIC 0x8a3e7fd1 + pthread_t thread; + pthread_mutex_t mtx; + pthread_cond_t cond; + char *vcl_name; + char *addr; + char *port; + double ttl; + VTAILQ_HEAD(,dns_director) director_list; + struct dns_director *next; + struct director dir; + struct vcl *vcl; + int active; + unsigned mark; +}; + +static void +vmod_dns_rotate(struct vmod_directors_dns *dns) +{ + if (dns->next != NULL) + dns->next = dns->next->list.vtqe_next; + + if (dns->next == NULL) + dns->next = dns->director_list.vtqh_first; +} + +static int +vmod_dns_notfound(struct vmod_directors_dns *dns, struct suckaddr *sa) +{ + struct dns_director *d; + + AN(sa); + + VTAILQ_FOREACH(d, &dns->director_list, list) { + if (d->mark == dns->mark) + continue; + + if (d->vrt->ipv4_suckaddr != NULL && + VSA_Compare(d->vrt->ipv4_suckaddr, sa)) + continue; + + if (d->vrt->ipv6_suckaddr != NULL && + VSA_Compare(d->vrt->ipv6_suckaddr, sa)) + continue; + + d->mark = dns->mark; + return (0); + } + + return (1); +} + +static unsigned __match_proto__(vdi_healthy_f) +vmod_dns_healthy(const struct director *d, const struct busyobj *bo, + double *changed) +{ + struct vmod_directors_dns *dns; + unsigned healthy; + + CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC); + CAST_OBJ_NOTNULL(dns, d->priv, VMOD_DIRECTORS_DNS_MAGIC); + + AZ(pthread_mutex_lock(&dns->mtx)); + (void)changed; + healthy = (dns->next != NULL); + AZ(pthread_mutex_unlock(&dns->mtx)); + + // XXX we can do better than that + return (healthy); +} + +static const struct director * __match_proto__(vdi_resolve_f) +vmod_dns_resolve(const struct director *d, struct worker *wrk, + struct busyobj *bo) +{ + struct vmod_directors_dns *dns; + struct director *dir = NULL; + + CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC); + CAST_OBJ_NOTNULL(dns, d->priv, VMOD_DIRECTORS_DNS_MAGIC); + + pthread_mutex_lock(&dns->mtx); + if (dns->next != NULL) { + dir = dns->next->simple_dir; + vmod_dns_rotate(dns); + } + AZ(pthread_mutex_unlock(&dns->mtx)); + + return (dir); +} + +static void +vmod_dns_del(VRT_CTX, struct dns_director *dir) +{ + struct dns_director *next; + struct vmod_directors_dns *dns; + + AN(dir); + AN(dir->dns); + + dns = dir->dns; + next = dns->next; + AN(next); + + if (dir == next) { + vmod_dns_rotate(dns); + if (next == dns->next) + dns->next = NULL; + } + + VTAILQ_REMOVE(&dir->dns->director_list, dir, list); + if (ctx) { + AN(ctx->vcl); + VRT_delete_backend(ctx, &dir->simple_dir); + } + free(dir); +} + +static void +vmod_dns_add(VRT_CTX, struct vmod_directors_dns *dns, + struct suckaddr *sa) +{ + struct dns_director *dir; + const unsigned char *ptr = NULL; + int af; + socklen_t len; + + dir = malloc(sizeof *dir); + AN(dir); + dir->dns = dns; + dir->mark = dns->mark; + + ALLOC_OBJ(dir->vrt, VRT_BACKEND_MAGIC); + AN(dir->vrt); + dir->vrt->port = dns->port; + dir->vrt->vcl_name = dns->vcl_name; + dir->vrt->hosthdr = dns->addr; + + af = VRT_VSA_GetPtr(sa, &ptr); + AN(ptr); + len = VSA_Len(sa); + AN(inet_ntop(af, ptr, dir->ip_addr, len)); + + switch (af) { + case AF_INET: + dir->vrt->ipv4_suckaddr = sa; + dir->vrt->ipv4_addr = dir->ip_addr; + break; + case AF_INET6: + dir->vrt->ipv6_suckaddr = sa; + dir->vrt->ipv6_addr = dir->ip_addr; + break; + default: + WRONG("unexpected family"); + } + + dir->simple_dir = VRT_new_backend(ctx, dir->vrt); + AN(dir->simple_dir); + + VTAILQ_INSERT_TAIL(&dns->director_list, dir, list); +} + +static void +vmod_dns_update(struct vmod_directors_dns *dns, struct addrinfo *addr) +{ + struct suckaddr *sa; + struct dns_director *d, *d2; + struct vrt_ctx ctx; + + AN(addr); + + INIT_OBJ(&ctx, VRT_CTX_MAGIC); + ctx.vcl = dns->vcl; + + dns->mark++; + while (addr) { + switch (addr->ai_family) { + case AF_INET: + case AF_INET6: + sa = malloc(vsa_suckaddr_len); + AN(sa); + AN(VSA_Build(sa, addr->ai_addr, addr->ai_addrlen)); + if (vmod_dns_notfound(dns, sa)) + vmod_dns_add(&ctx, dns, sa); + } + addr = addr->ai_next; + } + + VTAILQ_FOREACH_SAFE(d, &dns->director_list, list, d2) + if (d->mark != dns->mark) + vmod_dns_del(&ctx, d); + + vmod_dns_rotate(dns); +} + +static void* +vmod_dns_lookup_thread(void *obj) +{ + struct vmod_directors_dns *dns; + struct timespec ts; + struct addrinfo hints, *res; + double deadline; + int ret; + + CAST_OBJ_NOTNULL(dns, obj, VMOD_DIRECTORS_DNS_MAGIC); + + memset(&hints, 0, sizeof hints); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + while (dns->active) { + ret = getaddrinfo(dns->addr, dns->port, &hints, &res); + + AZ(pthread_mutex_lock(&dns->mtx)); + + if (ret == 0) { + vmod_dns_update(dns, res); + freeaddrinfo(res); + } + else + VSL(SLT_Error, 0, "DNS lookup failed: %d (%s)", + ret, gai_strerror(ret)); + + deadline = VTIM_real() + dns->ttl; + ts = VTIM_timespec(deadline); + ret = pthread_cond_timedwait(&dns->cond, &dns->mtx, &ts); + assert(ret == 0 || ret == ETIMEDOUT); + + AZ(pthread_mutex_unlock(&dns->mtx)); + } + + return (NULL); +} + +VCL_VOID __match_proto__() +vmod_dns__init(VRT_CTX, struct vmod_directors_dns **dnsp, const char *vcl_name, + VCL_STRING addr, VCL_STRING port) +{ + struct vmod_directors_dns *dns; + + ASSERT_CLI(); + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + AN(dnsp); + AZ(*dnsp); + AN(vcl_name); + AN(addr); + AN(port); + + ALLOC_OBJ(dns, VMOD_DIRECTORS_DNS_MAGIC); + AN(dns); + VTAILQ_INIT(&dns->director_list); + REPLACE(dns->vcl_name, vcl_name); + REPLACE(dns->addr, addr); + REPLACE(dns->port, port); + dns->vcl = ctx->vcl; + dns->ttl = 3600; + dns->active = 1; + + INIT_OBJ(&dns->dir, DIRECTOR_MAGIC); + dns->dir.name = "dns"; + dns->dir.vcl_name = dns->vcl_name; + dns->dir.healthy = vmod_dns_healthy; + dns->dir.resolve = vmod_dns_resolve; + dns->dir.priv = dns; + + AZ(pthread_mutex_init(&dns->mtx, NULL)); + AZ(pthread_cond_init(&dns->cond, NULL)); + AZ(pthread_create(&dns->thread, NULL, &vmod_dns_lookup_thread, dns)); + + *dnsp = dns; +} + +VCL_VOID __match_proto__() +vmod_dns__fini(struct vmod_directors_dns **dnsp) +{ + struct vmod_directors_dns *dns; + + AN(dnsp); + dns = *dnsp; + *dnsp = NULL; + + CHECK_OBJ_NOTNULL(dns, VMOD_DIRECTORS_DNS_MAGIC); + + AZ(pthread_mutex_lock(&dns->mtx)); + dns->active = 0; + AZ(pthread_mutex_unlock(&dns->mtx)); + + AZ(pthread_cond_signal(&dns->cond)); + AZ(pthread_join(dns->thread, NULL)); + + /* backends will be deleted by the VCL, pass NULL struct ctx */ + while (dns->director_list.vtqh_first != NULL) + vmod_dns_del(NULL, dns->director_list.vtqh_first); + + AZ(pthread_cond_destroy(&dns->cond)); + AZ(pthread_mutex_destroy(&dns->mtx)); + free(dns->vcl_name); + free(dns->addr); + free(dns->port); + FREE_OBJ(dns); +} + +VCL_VOID __match_proto__() +vmod_dns_set_ttl(VRT_CTX, struct vmod_directors_dns *dns, VCL_DURATION ttl) +{ + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(dns, VMOD_DIRECTORS_DNS_MAGIC); + assert(ttl > 0); + dns->ttl = ttl; +} + +VCL_BACKEND __match_proto__() +vmod_dns_backend(VRT_CTX, struct vmod_directors_dns *dns) +{ + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(dns, VMOD_DIRECTORS_DNS_MAGIC); + return (&dns->dir); +} diff --git a/lib/libvmod_directors/vmod.vcc b/lib/libvmod_directors/vmod.vcc index 2e958a7..411a87d 100644 --- a/lib/libvmod_directors/vmod.vcc +++ b/lib/libvmod_directors/vmod.vcc @@ -180,3 +180,46 @@ Description Example # pick a backend based on the cookie header from the client set req.backend_hint = vdir.backend(req.http.cookie); + +$Object dns(STRING, STRING) + +Description + Create a DNS director. + + The director creates backends with DNS lookups and chooses them in a + round robin fashion. An ACL (Access Control List) can be used as a + white-list to restrict the use of resolved addresses. + +Example + :: + + acl www_backends { + "192.168.15.0"/24; + !"192.168.15.1"; + } + + sub vcl_init { + new www_dir = directors.dns("www.example.com", "80"); + www_dir.restrict_to(www_backends); + www_dir.set_ttl(5m); + } + + sub vcl_recv { + set req.backend_hint = www_dir.backend(); + } + +# TODO $Method VOID .restrict_to(ACL) +# +#Description +# Restrict usage of resolved IP addresses to a named ACL. + +$Method VOID .set_ttl(DURATION) + +Description + Set the DNS lookup TTL (defaults to one hour). + +$Method BACKEND .backend() + +Description + Pick a backend from the director. + -- 2.1.0
_______________________________________________ varnish-dev mailing list [email protected] https://www.varnish-cache.org/lists/mailman/listinfo/varnish-dev
