Commit 9b8f6e21 authored by Nils Goroll's avatar Nils Goroll

shard director: LAZY mode (vdi resolve function), parameter objects

We introduce a shard_param object to hold the shard director lookup
parameters which until now could only be passed to the .backend()
method.

By associating a parameter object with a shard director, we enable
LAZY lookups as with the other directors. Parameter objects are
defined with VCL scope (normal vmod objects), but can be overridden
per backend request using a task priv.

We use the same concept to carry shard.backend() parameters to
vdi resolve for LAZY mode: They get saved in a per-director task
scope parameter object.

Each object points to another object providing defaults for
values which are not defined.

Actual resolution of the various parameter objects does not happen
before they are used to allow changing them independently (ie,
shard .backend() parameters have precedence over an associated
parameter object, which by itself can be overridden).

Overview of parameter objects (pointers are alternatives)

shard() director	shard_param() object	default praram

	     --------------------------------->	  vmod static
  VCL obj   /				     ->
  .param  -+--------->	  VCL obj	    /  _
  			  .default  --------   /|
					      /
			     ^		     /
			     |		    /
					   /
			  .default	  /
	------------->	  TASK priv	 /
       /				/
  .default -----------------------------
  TASK priv
parent 9533146f
varnishtest "shard director parameters"
server s1 -repeat 20 {
rxreq
txresp
} -start
varnish v1 -vcl+backend {
import directors;
import blob;
sub vcl_init {
new shard = directors.shard();
new p_def = directors.shard_param();
new p_hash = directors.shard_param();
p_hash.set(by=HASH, alt=1);
new p_url = directors.shard_param();
p_url.set(by=URL, warmup=0.5);
new p_key = directors.shard_param();
p_key.set(by=KEY, key=5, rampup=false);
new p_blob = directors.shard_param();
p_blob.set(by=BLOB, healthy=IGNORE,
key_blob=blob.decode(HEX, encoded="ffffffff00"));
}
sub vcl_synth {
if (req.url == "/def") {
set resp.http.sha256 = shard.key(req.url);
set resp.http.by = p_def.get_by();
set resp.http.key = p_def.get_key();
set resp.http.alt = p_def.get_alt();
set resp.http.warmup = p_def.get_warmup();
set resp.http.rampup = p_def.get_rampup();
set resp.http.healthy = p_def.get_healthy();
} else
if (req.url == "/hash") {
set resp.http.sha256 = shard.key(req.url);
set resp.http.by = p_hash.get_by();
set resp.http.key = p_hash.get_key();
set resp.http.alt = p_hash.get_alt();
set resp.http.warmup = p_hash.get_warmup();
set resp.http.rampup = p_hash.get_rampup();
set resp.http.healthy = p_hash.get_healthy();
} else
if (req.url == "/url") {
set resp.http.sha256 = shard.key(req.url);
set resp.http.by = p_url.get_by();
set resp.http.key = p_url.get_key();
set resp.http.alt = p_url.get_alt();
set resp.http.warmup = p_url.get_warmup();
set resp.http.rampup = p_url.get_rampup();
set resp.http.healthy = p_url.get_healthy();
} else
if (req.url == "/key") {
set resp.http.by = p_key.get_by();
set resp.http.key = p_key.get_key();
set resp.http.alt = p_key.get_alt();
set resp.http.warmup = p_key.get_warmup();
set resp.http.rampup = p_key.get_rampup();
set resp.http.healthy = p_key.get_healthy();
} else
if (req.url == "/blob") {
set resp.http.by = p_blob.get_by();
set resp.http.key = p_blob.get_key();
set resp.http.alt = p_blob.get_alt();
set resp.http.warmup = p_blob.get_warmup();
set resp.http.rampup = p_blob.get_rampup();
set resp.http.healthy = p_blob.get_healthy();
}
}
sub vcl_backend_response {
# overriding things
if (bereq.url ~ "^/b/c/hash/") {
set beresp.http.override = bereq.url;
p_def.set(by=HASH, alt=7);
p_hash.set(by=HASH, alt=8);
p_url.set(by=HASH, alt=9);
p_key.set(by=HASH, alt=10);
p_blob.set(by=HASH, alt=11);
}
if (bereq.url ~ "^/b/c/url/") {
set beresp.http.override = bereq.url;
p_def.set(by=URL, warmup=0.7);
p_hash.set(by=URL, warmup=0.8);
p_url.set(by=URL, rampup=false);
p_key.set(by=URL, healthy=ALL);
p_blob.set(by=URL, warmup=0.9);
}
if (bereq.url ~ "^/b/c/key/") {
set beresp.http.override = bereq.url;
p_def.set(by=KEY, key=7);
p_hash.set(by=KEY, key=8);
p_url.set(by=KEY, key=9);
p_key.set(by=KEY, key=10);
p_blob.set(by=KEY, key=11);
}
if (bereq.url ~ "/hash|/def") {
set beresp.http.hash = blob.encode(HEX, blob=bereq.hash);
}
if (bereq.url ~ "/url") {
set beresp.http.sha256 = shard.key(bereq.url);
}
if (bereq.url ~ "/def$") {
set beresp.http.by = p_def.get_by();
set beresp.http.key = p_def.get_key();
set beresp.http.alt = p_def.get_alt();
set beresp.http.warmup = p_def.get_warmup();
set beresp.http.rampup = p_def.get_rampup();
set beresp.http.healthy = p_def.get_healthy();
} else
if (bereq.url ~ "/hash$") {
set beresp.http.by = p_hash.get_by();
set beresp.http.key = p_hash.get_key();
set beresp.http.alt = p_hash.get_alt();
set beresp.http.warmup = p_hash.get_warmup();
set beresp.http.rampup = p_hash.get_rampup();
set beresp.http.healthy = p_hash.get_healthy();
} else
if (bereq.url ~ "/url$") {
set beresp.http.by = p_url.get_by();
set beresp.http.key = p_url.get_key();
set beresp.http.alt = p_url.get_alt();
set beresp.http.warmup = p_url.get_warmup();
set beresp.http.rampup = p_url.get_rampup();
set beresp.http.healthy = p_url.get_healthy();
} else
if (bereq.url ~ "/key$") {
set beresp.http.by = p_key.get_by();
set beresp.http.key = p_key.get_key();
set beresp.http.alt = p_key.get_alt();
set beresp.http.warmup = p_key.get_warmup();
set beresp.http.rampup = p_key.get_rampup();
set beresp.http.healthy = p_key.get_healthy();
} else
if (bereq.url ~ "/blob$") {
set beresp.http.by = p_blob.get_by();
set beresp.http.key = p_blob.get_key();
set beresp.http.alt = p_blob.get_alt();
set beresp.http.warmup = p_blob.get_warmup();
set beresp.http.rampup = p_blob.get_rampup();
set beresp.http.healthy = p_blob.get_healthy();
}
}
sub vcl_recv {
if (req.url ~ "^/b/") {
return (pass);
}
return (synth(200));
}
} -start
client c1 {
txreq -url /def
rxresp
expect resp.http.sha256 == 2002449278
expect resp.http.by == "HASH"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /hash
rxresp
expect resp.http.sha256 == 2439792896
expect resp.http.by == "HASH"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 1
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
# for client side, HASH = URL
txreq -url /url
rxresp
expect resp.http.sha256 == 3281611639
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "0.500"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /key
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 5
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "false"
expect resp.http.healthy == "CHOSEN"
txreq -url /blob
rxresp
expect resp.http.by == "BLOB"
expect resp.http.key == 4294967295
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "IGNORE"
} -run
client c2 {
txreq -url /b/def
rxresp
expect resp.http.hash == "93d1c4ad76396c91dd97fa310f7f26445332662c89393dbeeb77fe49f9111ee4"
expect resp.http.by == "HASH"
# == 0x93d1c4ad
expect resp.http.key == 2479998125
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/hash
rxresp
expect resp.http.hash == "e47da20ea4db49d4f22acdadc69f02f445002be520a2865cd3351272add62540"
expect resp.http.by == "HASH"
# == 0xe47da20e
expect resp.http.key == 3833438734
expect resp.http.alt == 1
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/url
rxresp
expect resp.http.sha256 == 108501858
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "0.500"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/key
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 5
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "false"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/blob
rxresp
expect resp.http.by == "BLOB"
expect resp.http.key == 4294967295
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "IGNORE"
} -run
client c3 {
txreq -url /b/c/hash/def
rxresp
expect resp.http.hash == "df9a465f8a0455c334b24c1638d3adda0f6e64fbe759029ab83602e3b9138884"
expect resp.http.by == "HASH"
# == 0xdf9a465f
expect resp.http.key == 3751429727
expect resp.http.alt == 7
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/hash/hash
rxresp
expect resp.http.hash == "0eb35bc1fab5aad5902fd1bac86540bd13d43aa31c6c46f54e776b43392e66e6"
expect resp.http.by == "HASH"
# == 0x0eb35bc1
expect resp.http.key == 246635457
expect resp.http.alt == 8
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/hash/url
rxresp
expect resp.http.hash == "1eb67b701ea07151cac5bea1f11b6267b9de15a3ff83cec995590480cbc2c750"
expect resp.http.by == "HASH"
# == 0x1eb67b70
expect resp.http.key == 515275632
expect resp.http.alt == 9
expect resp.http.warmup == "0.500"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/hash/key
rxresp
expect resp.http.hash == "a11b617e21aa7db22b6205d7612002e595b1b00d8c11602017f65456a1be3a35"
expect resp.http.by == "HASH"
# == 0xa11b617e
expect resp.http.key == 2702926206
expect resp.http.alt == 10
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "false"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/hash/blob
rxresp
expect resp.http.hash == "d7eecc0ac83e1727332dcd8c7c8ae9f3114123abb2bf7e3fb15ecea8c84bb239"
expect resp.http.by == "HASH"
# == 0xd7eecc0a
expect resp.http.key == 3622751242
expect resp.http.alt == 11
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "IGNORE"
} -run
client c3 {
txreq -url /b/c/url/def
rxresp
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "0.700"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/url/hash
rxresp
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 1
expect resp.http.warmup == "0.800"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/url/url
rxresp
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "0.500"
expect resp.http.rampup == "false"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/url/key
rxresp
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "false"
expect resp.http.healthy == "ALL"
txreq -url /b/c/url/blob
rxresp
expect resp.http.by == "URL"
expect resp.http.key == resp.http.sha256
expect resp.http.alt == 0
expect resp.http.warmup == "0.900"
expect resp.http.rampup == "true"
expect resp.http.healthy == "IGNORE"
} -run
client c4 {
txreq -url /b/c/key/def
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 7
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/key/hash
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 8
expect resp.http.alt == 1
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/key/url
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 9
expect resp.http.alt == 0
expect resp.http.warmup == "0.500"
expect resp.http.rampup == "true"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/key/key
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 10
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "false"
expect resp.http.healthy == "CHOSEN"
txreq -url /b/c/key/blob
rxresp
expect resp.http.by == "KEY"
expect resp.http.key == 11
expect resp.http.alt == 0
expect resp.http.warmup == "-1.000"
expect resp.http.rampup == "true"
expect resp.http.healthy == "IGNORE"
} -run
varnishtest "shard director LAZY"
server s1 {
rxreq
txresp -body "ech3Ooj"
} -start
server s2 {
rxreq
txresp -body "ieQu2qua"
} -start
server s3 {
rxreq
txresp -body "xiuFi3Pe"
} -start
varnish v1 -vcl+backend {
import std;
import directors;
sub vcl_init {
new vd = directors.shard();
if (!vd.add_backend(s1)) {
std.log("add s1 failed");
}
if (!vd.add_backend(s2)) {
std.log("add s2 failed");
}
if (!vd.add_backend(s3)) {
std.log("add s3 failed");
}
if (!vd.reconfigure(replicas=25)) {
std.log("reconfigure failed");
}
}
sub vcl_recv {
return(pass);
}
sub vcl_backend_fetch {
if (bereq.url == "/1") {
set bereq.backend =
vd.backend(resolve=LAZY, by=KEY, key=1);
}
if (bereq.url == "/2") {
set bereq.backend =
vd.backend(resolve=LAZY, by=KEY, key=2147483647);
}
if (bereq.url == "/3") {
set bereq.backend =
vd.backend(resolve=LAZY, by=KEY, key=4294967295);
}
}
sub vcl_backend_response {
set beresp.http.healthy = std.healthy(
vd.backend(resolve=LAZY, by=KEY, key=1));
}
} -start
client c1 {
txreq -url /1
rxresp
expect resp.body == "ech3Ooj"
expect resp.http.healthy == "true"
txreq -url /2
rxresp
expect resp.body == "ieQu2qua"
expect resp.http.healthy == "true"
txreq -url /3
rxresp
expect resp.body == "xiuFi3Pe"
expect resp.http.healthy == "true"
} -run
varnishtest "shard director LAZY - d18.vtc"
server s1 {
rxreq
txresp -body "ech3Ooj"
} -start
server s2 {
rxreq
txresp -body "ieQu2qua"
} -start
server s3 {
rxreq
txresp -body "xiuFi3Pe"
} -start
varnish v1 -vcl+backend {
import std;
import directors;
sub vcl_init {
new vd = directors.shard();
if (!vd.add_backend(s1)) {
std.log("add s1 failed");
}
if (!vd.add_backend(s2)) {
std.log("add s2 failed");
}
if (!vd.add_backend(s3)) {
std.log("add s3 failed");
}
if (!vd.reconfigure(replicas=25)) {
std.log("reconfigure failed");
}
vd.debug(1);
new p = directors.shard_param();
p.set(by=KEY, key=1);
vd.associate(p.use());
new p3 = directors.shard_param();
p3.set(by=KEY, key=4294967295);
}
sub vcl_recv {
return(pass);
}
sub vcl_backend_fetch {
set bereq.backend=vd.backend(resolve=LAZY);
if (bereq.url == "/1") {
# default
} else
if (bereq.url == "/2") {
# backend override parameter set
p.set(by=KEY, key=2147483647);
} else
if (bereq.url == "/3") {
# backend override association
vd.backend(resolve=LAZY, param=p3.use());
}
}
sub vcl_backend_response {
set beresp.http.backend = bereq.backend;
}
} -start
client c1 {
txreq -url /1
rxresp
expect resp.body == "ech3Ooj"
expect resp.http.backend == "vd"
txreq -url /2
rxresp
expect resp.body == "ieQu2qua"
expect resp.http.backend == "vd"
txreq -url /3
rxresp
expect resp.body == "xiuFi3Pe"
expect resp.http.backend == "vd"
} -run
varnishtest "shard director error handling"
varnish v1 -vcl {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new shard = directors.shard();
new p = directors.shard_param();
p.set(by=BLOB, key_blob=blob.decode(HEX, encoded=""));
}
sub vcl_recv {
if (req.url == "/1") {
set req.backend_hint = shard.backend(
param=blob.decode(HEX, encoded=""));
} else if (req.url == "/2") {
p.set(by=HASH);
}
}
} -start
logexpect l1 -v v1 -g raw -d 1 {
expect 0 0 CLI {^Rd vcl.load}
expect 0 0 Error {by=BLOB but no or empty key_blob - using key 0}
} -start -wait
logexpect l2 -v v1 -g raw {
expect * 1001 VCL_Error {shard .backend param invalid}
} -start
logexpect l3 -v v1 -g raw {
expect * 1003 VCL_Error {shard_param.set.. may only be used in vcl_init and in backend context}
} -start
client c1 {
txreq -url "/1"
rxresp
expect resp.status == 503
expect_close
} -run
client c1 {
txreq -url "/2"
rxresp
expect resp.status == 503
expect_close
} -run
logexpect l2 -wait
logexpect l3 -wait
varnish v1 -errvcl {shard .associate param invalid} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new shard = directors.shard();
shard.associate(blob.decode(encoded=""));
}
}
varnish v1 -errvcl {missing key argument with by=KEY} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(by=KEY);
}
}
varnish v1 -errvcl {invalid key argument -5 with by=KEY} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(by=KEY, key=-5);
}
}
varnish v1 -errvcl {missing key_blob argument with by=BLOB} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(by=BLOB);
}
}
varnish v1 -errvcl {key and key_blob arguments are invalid with by=URL} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(by=URL, key=0);
}
}
varnish v1 -errvcl {key and key_blob arguments are invalid with by=HASH (default)} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(key=0);
}
}
varnish v1 -errvcl {invalid alt argument -1} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(alt=-1);
}
}
varnish v1 -errvcl {invalid warmup argument -0.5} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(warmup=-0.5);
}
}
varnish v1 -errvcl {invalid warmup argument 1.1} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new p = directors.shard_param();
p.set(warmup=1.1);
}
}
varnish v1 -errvcl {resolve=LAZY with other parameters can only be used in backend context} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new shard = directors.shard();
new rr = directors.round_robin();
rr.add_backend(shard.backend(resolve=LAZY, by=KEY));
}
}
varnish v1 -errvcl {resolve=NOW can not be used in vcl_init} {
import directors;
import blob;
backend dummy { .host = "${bad_backend}"; }
sub vcl_init {
new shard = directors.shard();
new rr = directors.round_robin();
rr.add_backend(shard.backend());
}
}
......@@ -104,6 +104,31 @@ VCL and bundled VMODs
<dir>.backend(by=BLOB, key_blob=blobdigest.hash(RS,
blob.decode(encoded=<string>)))
* The ``shard`` director now offers resolution at the time the actual
backend connection is made, which is how all other bundled directors
work as well: With the ``resolve=LAZY`` argument, other shard
parameters are saved for later reference and a director object is
returned.
This enables layering the shard director below other directors.
* The ``shard`` director now also supports getting other parameters
from a parameter set object: Rather than passing the required
parameters with each ``.backend()`` call, an object can be
associated with a shard director defining the parameters. The
association can be changed in ``vcl_backend_fetch()`` and individual
parameters can be overridden in each ``.backend()`` call.
The main use case is to segregate shard parameters from director
selection: By associating a parameter object with many directors,
the same load balancing decision can easily be applied independent
of which set of backends is to be used.
* To support parameter overriding, support for positional arguments of
the shard director ``.backend()`` method had to be removed. In other
words, all parameters to the shard director ``.backend()`` method
now need to be named.
Logging / statistics
--------------------
......
......@@ -13,7 +13,8 @@ libvmod_directors_la_SOURCES = \
shard_dir.c \
shard_dir.h \
tbl_by.h \
tbl_healthy.h
tbl_healthy.h \
tbl_resolve.h
# Use vmodtool.py generated automake boilerplate
include $(srcdir)/automake_boilerplate.am
......@@ -304,27 +304,39 @@ init_state(struct shard_state *state,
state->last.hostid = -1;
}
/* basically same as vdir_any_healthy
* - XXX we should embed a vdir
* - XXX should we return the health state of the actual backend
* for healthy=IGNORE ?
*/
unsigned
sharddir_any_healthy(struct sharddir *shardd, const struct busyobj *bo,
double *changed)
{
unsigned retval = 0;
VCL_BACKEND be;
unsigned u;
double c;
CHECK_OBJ_NOTNULL(shardd, SHARDDIR_MAGIC);
CHECK_OBJ_ORNULL(bo, BUSYOBJ_MAGIC);
sharddir_rdlock(shardd);
if (changed != NULL)
*changed = 0;
for (u = 0; u < shardd->n_backend; u++) {
be = shardd->backend[u].backend;
CHECK_OBJ_NOTNULL(be, DIRECTOR_MAGIC);
retval = be->healthy(be, bo, &c);
if (changed != NULL && c > *changed)
*changed = c;
if (retval)
break;
}
sharddir_unlock(shardd);
return (retval);
}
/*
* core function for the director backend method
*
* while other directors return a reference to their own backend object (on
* which varnish will call the resolve method to resolve to a non-director
* backend), this director immediately reolves in the backend method, to make
* the director choice visible in VCL
*
* consequences:
* - we need no own struct director
* - we can only respect a busy object when being called on the backend side,
* which probably is, for all practical purposes, only relevant when the
* saintmode vmod is used
*
* if we wanted to offer delayed resolution, we'd need something like
* per-request per-director state or we'd need to return a dynamically created
* director object. That should be straight forward once we got director
* refcounting #2072. Until then, we could create it on the workspace, but then
* we'd need to keep other directors from storing any references to our dynamic
* object for longer than the current task
*
* core function for the director backend/resolve method
*/
VCL_BACKEND
sharddir_pick_be(VRT_CTX, struct sharddir *shardd,
......
......@@ -41,6 +41,13 @@ enum healthy_e {
_HEALTHY_E_MAX
};
enum resolve_e {
_RESOLVE_E_INVALID = 0,
#define VMODENUM(x) x,
#include "tbl_resolve.h"
_RESOLVE_E_MAX
};
struct vbitmap;
struct shard_circlepoint {
......@@ -121,6 +128,8 @@ void sharddir_new(struct sharddir **sharddp, const char *vcl_name);
void sharddir_delete(struct sharddir **sharddp);
void sharddir_wrlock(struct sharddir *shardd);
void sharddir_unlock(struct sharddir *shardd);
unsigned sharddir_any_healthy(struct sharddir *shardd, const struct busyobj *bo,
double *changed);
VCL_BACKEND sharddir_pick_be(VRT_CTX, struct sharddir *, uint32_t, VCL_INT,
VCL_REAL, VCL_BOOL, enum healthy_e);
......
VMODENUM(NOW)
VMODENUM(LAZY)
#undef VMODENUM
......@@ -3,7 +3,7 @@
# itself. See LICENCE for details.
#
# Copyright (c) 2013-2015 Varnish Software AS
# Copyright 2009-2016 UPLEX - Nils Goroll Systemoptimierung
# Copyright 2009-2018 UPLEX - Nils Goroll Systemoptimierung
# All rights reserved.
#
# Authors: Poul-Henning Kamp <phk@FreeBSD.org>
......@@ -233,6 +233,9 @@ Note that the shard director needs to be configured using at least one
``shard.add_backend()`` call(s) **followed by a**
``shard.reconfigure()`` **call** before it can hand out backends.
_Note_ that due to various restrictions (documented below), it is
recommended to use the shard director on the backend side.
Introduction
````````````
......@@ -354,6 +357,16 @@ Set the default rampup duration. See `rampup` parameter of
Default: 0s (no rampup)
$Method VOID .associate(BLOB param=0)
Associate a default `obj_shard_param`_ object or clear an association.
The value of the `param` argument must be a call to the
`func_shard_param.use`_ method. No argument clears the association.
The association can be changed per backend request using the `param`
argument of `func_shard.backend`_.
$Method BOOL .add_backend(PRIV_TASK, BACKEND backend,
STRING ident=0, DURATION rampup=973279260)
......@@ -409,14 +422,15 @@ To generate sharding keys using other hashes, use a custom vmod like
.. _vmod blobdigest: https://code.uplex.de/uplex-varnish/libvmod-blobdigest/blob/master/README.rst
$Method BACKEND .backend(
ENUM {HASH, URL, KEY, BLOB} by=HASH,
INT key=0,
BLOB key_blob=0,
INT alt=0,
REAL warmup=-1,
BOOL rampup=1,
ENUM {CHOSEN, IGNORE, ALL} healthy=CHOSEN)
[ ENUM {HASH, URL, KEY, BLOB} by ],
[ INT key ],
[ BLOB key_blob ],
[ INT alt ],
[ REAL warmup ],
[ BOOL rampup ],
[ ENUM {CHOSEN, IGNORE, ALL} healthy ],
[ BLOB param ],
[ ENUM {NOW, LAZY} resolve] )
Lookup a backend on the consistent hashing ring.
......@@ -519,15 +533,149 @@ is _not_ the order given when backends are added.
For `alt > 0`, return the `alt`-th alternative backend of all
those healthy, the last healthy backend found or none.
* `resolve`
* ``NOW``: look up a backend and return it.
Can not be used in ``vcl_init{}``.
* ``LAZY``: return an instance of this director for later backend resolution.
``LAZY`` mode is required for referencing shard director instances,
for example as backends for other directors (director layering).
In ``vcl_init{}`` and on the client side, ``LAZY`` mode can not be
used with any other argument.
On the backend side, parameters from arguments or an associated
parameter set affect the shard director instance for the backend
request irrespective of where it is referenced.
* `param`
Use or associate a parameter set. The value of the `param` argument
must be a call to the `func_shard_param.use`_ method.
default: as set by `func_shard.associate`_ or unset.
* for ``resolve=NOW`` take parameter defaults from the
`obj_shard_param`_ parameter set
* for ``resolve=LAZY`` associate the `obj_shard_param`_ parameter
set for this backend request
Implementation notes for use of parameter sets with
``resolve=LAZY``:
* A `param` argument remains associated and any changes to the
associated parameter set affect the sharding decision once the
director resolves to an actual backend.
* If other paramter arguments are also given, they have preference
and are kept even if the parameter set given by the `param`
argument is subsequently changed within the same backend request.
* Each call to `func_shard.backend`_ overrides any previous call.
$Method VOID .debug(INT)
`intentionally undocumented`
$Object shard_param()
Create a shard parameter set.
A parameter set allows for re-use of `func_shard.backend`_ arguments
across many shard director instances and simplifies advanced use cases
(e.g. shard director with custom parameters layered below other
directors).
Parameter sets have two scopes:
* per-VCL scope defined in ``vcl_init{}``
* per backend request scope
The per-VCL scope defines defaults for the per backend scope. Any
changes to a parameter set in backend context only affect the
respective backend request.
Parameter sets can not be used in client context.
$Method VOID .clear()
Reset the parameter set to default values as documented for
`func_shard.backend`_.
* in ``vcl_init{}``, resets the parameter set default for this VCL
* in backend context, resets the parameter set for this backend
request to the VCL defaults
This method may not be used in client context
$Method VOID .set(
[ ENUM {HASH, URL, KEY, BLOB} by ],
[ INT key ],
[ BLOB key_blob ],
[ INT alt ],
[ REAL warmup ],
[ BOOL rampup ],
[ ENUM {CHOSEN, IGNORE, ALL} healthy ])
Change the given parameters of a parameter set as documented for
`func_shard.backend`_.
* in ``vcl_init{}``, changes the parameter set default for this VCL
* in backend context, changes the parameter set for this backend
request, keeping the defaults set for this VCL for unspecified
arguments.
This method may not be used in client context
$Method STRING .get_by()
Get a string representation of the `by` enum argument which denotes
how a shard director using this parameter object would derive the
shard key. See `func_shard.backend`_.
$Method INT .get_key()
Get the key which a shard director using this parameter object would
use. See `func_shard.backend`_.
$Method INT .get_alt()
Get the `alt` paramter which a shard director using this parameter
object would use. See `func_shard.backend`_.
$Method REAL .get_warmup()
Get the `warmup` paramter which a shard director using this parameter
object would use. See `func_shard.backend`_.
$Method BOOL .get_rampup()
Get the `rampup` paramter which a shard director using this parameter
object would use. See `func_shard.backend`_.
$Method STRING .get_healthy()
Get a string representation of the `healthy` enum argument which a
shard director using this parameter object would use. See
`func_shard.backend`_.
$Method BLOB .use()
This method may only be used in backend context.
For use with the `param` argument of `func_shard.backend`_ to associate
this shard parameter set with a shard director.
ACKNOWLEDGEMENTS
================
Development of a previous version of the shard director was partly sponsored
by Deutsche Telekom AG - Products & Innovation.
Development of a previous version of the shard director was partly
sponsored by Deutsche Telekom AG - Products & Innovation.
Development of this version of the shard director was partly sponsored
by BILD GmbH & Co KG.
Development of a previous version of the shard director was partly
sponsored by BILD GmbH & Co KG.
/*-
* Copyright 2009-2016 UPLEX - Nils Goroll Systemoptimierung
* Copyright 2009-2018 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
*
* Authors: Julian Wiesener <jw@uplex.de>
......@@ -33,6 +33,7 @@
#include <string.h>
#include "cache/cache.h"
#include "cache/cache_director.h"
#include "vcl.h"
#include "vend.h"
......@@ -41,17 +42,149 @@
#include "shard_dir.h"
#include "shard_cfg.h"
/* -------------------------------------------------------------------------
* method arguments and set parameters bitmask in vmod_directors_shard_param
*/
#define arg_by ((uint32_t)1)
#define arg_key ((uint32_t)1 << 1)
#define arg_key_blob ((uint32_t)1 << 2)
#define arg_alt ((uint32_t)1 << 3)
#define arg_warmup ((uint32_t)1 << 4)
#define arg_rampup ((uint32_t)1 << 5)
#define arg_healthy ((uint32_t)1 << 6)
#define arg_param ((uint32_t)1 << 7)
#define arg_resolve ((uint32_t)1 << 8)
#define _arg_mask ((arg_resolve << 1) - 1)
/* allowed in shard_param.set */
#define _arg_mask_set (arg_param - 1)
/* allowed in shard_param */
#define _arg_mask_param ( _arg_mask_set \
& ~arg_key \
& ~arg_key_blob )
/* -------------------------------------------------------------------------
* shard parameters - declaration & defaults
*/
enum vmod_directors_shard_param_scope {
_SCOPE_INVALID = 0,
VMOD,
VCL,
TASK,
STACK
};
struct vmod_directors_shard_param;
struct vmod_directors_shard_param {
unsigned magic;
#define VMOD_SHARD_SHARD_PARAM_MAGIC 0xdf5ca117
/* internals */
uint32_t key;
const char *vcl_name;
const struct vmod_directors_shard_param *defaults;
enum vmod_directors_shard_param_scope scope;
/* paramters */
enum by_e by;
enum healthy_e healthy;
uint32_t mask;
VCL_BOOL rampup;
VCL_INT alt;
VCL_REAL warmup;
};
static const struct vmod_directors_shard_param shard_param_default = {
.magic = VMOD_SHARD_SHARD_PARAM_MAGIC,
.key = 0,
.vcl_name = "builtin defaults",
.defaults = NULL,
.scope = VMOD,
.mask = _arg_mask_param,
.by = BY_HASH,
.healthy = CHOSEN,
.rampup = 1,
.alt = 0,
.warmup = -1,
};
static struct vmod_directors_shard_param *
shard_param_stack(struct vmod_directors_shard_param *p,
const struct vmod_directors_shard_param *pa, const char *who);
static struct vmod_directors_shard_param *
shard_param_task(VRT_CTX, const void *id,
const struct vmod_directors_shard_param *pa);
static const struct vmod_directors_shard_param *
shard_param_blob(const VCL_BLOB blob);
static const struct vmod_directors_shard_param *
vmod_shard_param_read(VRT_CTX, const void *id,
const struct vmod_directors_shard_param *p,
struct vmod_directors_shard_param *pstk, const char *who);
/* -------------------------------------------------------------------------
* shard vmod interface
*/
static unsigned v_matchproto_(vdi_healthy)
vmod_shard_healthy(const struct director *dir, const struct busyobj *bo,
double *changed);
static const struct director * v_matchproto_(vdi_resolve_f)
vmod_shard_resolve(const struct director *dir, struct worker *wrk,
struct busyobj *bo);
struct vmod_directors_shard {
unsigned magic;
#define VMOD_SHARD_SHARD_MAGIC 0x6e63e1bf
struct sharddir *shardd;
unsigned magic;
#define VMOD_SHARD_SHARD_MAGIC 0x6e63e1bf
struct sharddir *shardd;
struct director *dir;
const struct vmod_directors_shard_param *param;
};
VCL_VOID v_matchproto_(td_directors_shard__init)
vmod_shard__init(VRT_CTX, struct vmod_directors_shard **vshardp,
const char *vcl_name)
static enum by_e
parse_by_e(VCL_ENUM e)
{
#define VMODENUM(n) if (e == vmod_enum_ ## n) return(BY_ ## n);
#include "tbl_by.h"
WRONG("illegal by enum");
}
static enum healthy_e
parse_healthy_e(VCL_ENUM e)
{
#define VMODENUM(n) if (e == vmod_enum_ ## n) return(n);
#include "tbl_healthy.h"
WRONG("illegal healthy enum");
}
static enum resolve_e
parse_resolve_e(VCL_ENUM e)
{
#define VMODENUM(n) if (e == vmod_enum_ ## n) return(n);
#include "tbl_resolve.h"
WRONG("illegal resolve enum");
}
static const char * const by_str[_BY_E_MAX] = {
[_BY_E_INVALID] = "*INVALID*",
#define VMODENUM(n) [BY_ ## n] = #n,
#include "tbl_by.h"
};
static const char * const healthy_str[_HEALTHY_E_MAX] = {
[_HEALTHY_E_INVALID] = "*INVALID*",
#define VMODENUM(n) [n] = #n,
#include "tbl_healthy.h"
};
static void
shard__assert(void)
{
struct vmod_directors_shard *vshard;
VCL_INT t1;
uint32_t t2a, t2b;
......@@ -61,7 +194,15 @@ vmod_shard__init(VRT_CTX, struct vmod_directors_shard **vshardp,
t1 = (VCL_INT)t2a;
t2b = (uint32_t)t1;
assert(t2a == t2b);
}
VCL_VOID v_matchproto_(td_directors_shard__init)
vmod_shard__init(VRT_CTX, struct vmod_directors_shard **vshardp,
const char *vcl_name)
{
struct vmod_directors_shard *vshard;
shard__assert();
(void)ctx;
AN(vshardp);
AZ(*vshardp);
......@@ -70,6 +211,15 @@ vmod_shard__init(VRT_CTX, struct vmod_directors_shard **vshardp,
*vshardp = vshard;
sharddir_new(&vshard->shardd, vcl_name);
vshard->param = &shard_param_default;
ALLOC_OBJ(vshard->dir, DIRECTOR_MAGIC);
AN(vshard->dir);
REPLACE(vshard->dir->vcl_name, vcl_name);
vshard->dir->priv = vshard;
vshard->dir->resolve = vmod_shard_resolve;
vshard->dir->healthy = vmod_shard_healthy;
vshard->dir->admin_health = VDI_AH_HEALTHY;
}
VCL_VOID v_matchproto_(td_directors_shard__fini)
......@@ -80,6 +230,8 @@ vmod_shard__fini(struct vmod_directors_shard **vshardp)
*vshardp = NULL;
CHECK_OBJ_NOTNULL(vshard, VMOD_SHARD_SHARD_MAGIC);
sharddir_delete(&vshard->shardd);
free(vshard->dir->vcl_name);
FREE_OBJ(vshard->dir);
FREE_OBJ(vshard);
}
......@@ -121,6 +273,28 @@ vmod_shard_set_rampup(VRT_CTX, struct vmod_directors_shard *vshard,
shardcfg_set_rampup(vshard->shardd, duration);
}
VCL_VOID v_matchproto_(td_directors_shard_associate)
vmod_shard_associate(VRT_CTX,
struct vmod_directors_shard *vshard, VCL_BLOB b)
{
const struct vmod_directors_shard_param *ppt;
CHECK_OBJ_NOTNULL(vshard, VMOD_SHARD_SHARD_MAGIC);
if (b == NULL) {
vshard->param = &shard_param_default;
return;
}
ppt = shard_param_blob(b);
if (ppt == NULL) {
VRT_fail(ctx, "shard .associate param invalid");
return;
}
vshard->param = ppt;
}
VCL_BOOL v_matchproto_(td_directors_shard_add_backend)
vmod_shard_add_backend(VRT_CTX, struct vmod_directors_shard *vshard,
struct vmod_priv *priv,
......@@ -172,14 +346,11 @@ vmod_shard_reconfigure(VRT_CTX, struct vmod_directors_shard *vshard,
}
static inline uint32_t
get_key(VRT_CTX, enum by_e by, VCL_INT key_int, VCL_BLOB key_blob)
shard_get_key(VRT_CTX, const struct vmod_directors_shard_param *p)
{
struct http *http;
uint8_t k[4] = { 0 };
uint8_t *b;
int i, ki;
switch (by) {
switch (p->by) {
case BY_HASH:
if (ctx->bo) {
CHECK_OBJ_NOTNULL(ctx->bo, BUSYOBJ_MAGIC);
......@@ -196,92 +367,355 @@ get_key(VRT_CTX, enum by_e by, VCL_INT key_int, VCL_BLOB key_blob)
return (sharddir_sha256(http->hd[HTTP_HDR_URL].b,
vrt_magic_string_end));
case BY_KEY:
return ((uint32_t)key_int);
case BY_BLOB:
assert(key_blob);
assert(key_blob->len > 0);
assert(key_blob->priv != NULL);
return (p->key);
default:
WRONG("by enum");
}
}
if (key_blob->len >= 4)
ki = 0;
else
ki = 4 - key_blob->len;
/*
* merge parameters to resolve all undef values
* key is to be calculated after merging
*/
static void
shard_param_merge(struct vmod_directors_shard_param *to,
const struct vmod_directors_shard_param *from)
{
CHECK_OBJ_NOTNULL(to, VMOD_SHARD_SHARD_PARAM_MAGIC);
assert((to->mask & ~_arg_mask_param) == 0);
b = key_blob->priv;
for (i = 0; ki < 4; i++, ki++)
k[ki] = b[i];
assert(i <= key_blob->len);
if (to->mask == _arg_mask_param)
return;
return (vbe32dec(k));
default:
WRONG("by value");
CHECK_OBJ_NOTNULL(from, VMOD_SHARD_SHARD_PARAM_MAGIC);
assert((from->mask & ~_arg_mask_param) == 0);
if ((to->mask & arg_by) == 0 && (from->mask & arg_by) != 0) {
to->by = from->by;
if (from->by == BY_KEY || from->by == BY_BLOB)
to->key = from->key;
}
#define mrg(to, from, field) do { \
if (((to)->mask & arg_ ## field) == 0 && \
((from)->mask & arg_ ## field) != 0) \
(to)->field = (from)->field; \
} while(0)
mrg(to, from, healthy);
mrg(to, from, rampup);
mrg(to, from, alt);
mrg(to, from, warmup);
#undef mrg
to->mask |= from->mask;
if (to->mask == _arg_mask_param)
return;
AN(from->defaults);
shard_param_merge(to, from->defaults);
}
static enum by_e
parse_by_e(VCL_ENUM e)
static uint32_t
shard_blob_key(VCL_BLOB key_blob)
{
#define VMODENUM(n) if (e == vmod_enum_ ## n) return(BY_ ## n);
#include "tbl_by.h"
WRONG("illegal by enum");
uint8_t k[4] = { 0 };
uint8_t *b;
int i, ki;
assert(key_blob);
assert(key_blob->len > 0);
assert(key_blob->priv != NULL);
if (key_blob->len >= 4)
ki = 0;
else
ki = 4 - key_blob->len;
b = key_blob->priv;
for (i = 0; ki < 4; i++, ki++)
k[ki] = b[i];
assert(i <= key_blob->len);
return (vbe32dec(k));
}
static enum healthy_e
parse_healthy_e(VCL_ENUM e)
/*
* convert vmod interface valid_* to our bitmask
*/
#define tobit(args, name) ((args)->valid_##name ? arg_##name : 0)
static uint32_t
shard_backend_arg_mask(const struct vmod_shard_backend_arg * const a)
{
#define VMODENUM(n) if (e == vmod_enum_ ## n) return(n);
#include "tbl_healthy.h"
WRONG("illegal healthy enum");
return (tobit(a, by) |
tobit(a, key) |
tobit(a, key_blob) |
tobit(a, alt) |
tobit(a, warmup) |
tobit(a, rampup) |
tobit(a, healthy) |
tobit(a, param) |
tobit(a, resolve));
}
static uint32_t
shard_param_set_mask(const struct vmod_shard_param_set_arg * const a)
{
return (tobit(a, by) |
tobit(a, key) |
tobit(a, key_blob) |
tobit(a, alt) |
tobit(a, warmup) |
tobit(a, rampup) |
tobit(a, healthy));
}
#undef tobit
/*
* check arguments and return in a struct param
*/
static struct vmod_directors_shard_param *
shard_param_args(VRT_CTX,
struct vmod_directors_shard_param *p, const char *who,
uint32_t args, VCL_ENUM by_s, VCL_INT key_int, VCL_BLOB key_blob,
VCL_INT alt, VCL_REAL warmup, VCL_BOOL rampup, VCL_ENUM healthy_s)
{
enum by_e by;
enum healthy_e healthy;
CHECK_OBJ_NOTNULL(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
AN(p->vcl_name);
assert((args & ~_arg_mask_set) == 0);
by = (args & arg_by) ? parse_by_e(by_s) : BY_HASH;
healthy = (args & arg_healthy) ? parse_healthy_e(healthy_s) : CHOSEN;
/* by_s / key_int / key_blob */
if (args & arg_by) {
switch (by) {
case BY_KEY:
if ((args & arg_key) == 0) {
VRT_fail(ctx, "%s %s: "
"missing key argument with by=%s",
who, p->vcl_name, by_s);
return (NULL);
}
if (key_int < 0 || key_int > UINT32_MAX) {
VRT_fail(ctx, "%s %s: "
"invalid key argument %ld with by=%s",
who, p->vcl_name, key_int, by_s);
return (NULL);
}
assert(key_int >= 0);
assert(key_int <= UINT32_MAX);
p->key = (uint32_t)key_int;
break;
case BY_BLOB:
if ((args & arg_key_blob) == 0) {
VRT_fail(ctx, "%s %s: "
"missing key_blob argument with by=%s",
who, p->vcl_name, by_s);
return (NULL);
}
if (key_blob == NULL || key_blob->len <= 0 ||
key_blob->priv == NULL) {
sharddir_err(ctx, SLT_Error, "%s %s: "
"by=BLOB but no or empty key_blob "
"- using key 0",
who, p->vcl_name);
p->key = 0;
} else
p->key = shard_blob_key(key_blob);
break;
case BY_HASH:
case BY_URL:
if (args & (arg_key|arg_key_blob)) {
VRT_fail(ctx, "%s %s: "
"key and key_blob arguments are "
"invalid with by=%s",
who, p->vcl_name, by_s);
return (NULL);
}
break;
default:
WRONG("by enum");
}
p->by = by;
} else {
/* (args & arg_by) == 0 */
p->by = BY_HASH;
if (args & (arg_key|arg_key_blob)) {
VRT_fail(ctx, "%s %s: "
"key and key_blob arguments are "
"invalid with by=HASH (default)",
who, p->vcl_name);
return (NULL);
}
}
if (args & arg_alt) {
if (alt < 0) {
VRT_fail(ctx, "%s %s: "
"invalid alt argument %ld",
who, p->vcl_name, alt);
return (NULL);
}
p->alt = alt;
}
if (args & arg_warmup) {
if ((warmup < 0 && warmup != -1) || warmup > 1) {
VRT_fail(ctx, "%s %s: "
"invalid warmup argument %f",
who, p->vcl_name, warmup);
return (NULL);
}
p->warmup = warmup;
}
if (args & arg_rampup)
p->rampup = !!rampup;
if (args & arg_healthy)
p->healthy = healthy;
p->mask = args & _arg_mask_param;
return (p);
}
VCL_BACKEND v_matchproto_(td_directors_shard_backend)
vmod_shard_backend(VRT_CTX, struct vmod_directors_shard *vshard,
VCL_ENUM by_s, VCL_INT key_int, VCL_BLOB key_blob, VCL_INT alt,
VCL_REAL warmup, VCL_BOOL rampup, VCL_ENUM healthy_s)
struct vmod_shard_backend_arg *a)
{
enum by_e by = parse_by_e(by_s);
enum healthy_e healthy = parse_healthy_e(healthy_s);
uint32_t key;
struct vmod_directors_shard_param pstk;
struct vmod_directors_shard_param *pp = NULL;
const struct vmod_directors_shard_param *ppt;
enum resolve_e resolve;
uint32_t args = shard_backend_arg_mask(a);
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(vshard, VMOD_SHARD_SHARD_MAGIC);
assert((args & ~_arg_mask) == 0);
resolve = (args & arg_resolve) ? parse_resolve_e(a->resolve) : NOW;
switch (resolve) {
case LAZY:
if ((ctx->method & VCL_MET_TASK_B) == 0) {
if ((args & ~arg_resolve) != 0) {
VRT_fail(ctx,
"shard .backend resolve=LAZY "
"with other parameters can "
"only be used in backend "
"context");
return (NULL);
}
AN(vshard->dir);
return (vshard->dir);
}
/* TODO #2500 */
if ((ctx->method & (VCL_MET_TASK_C | VCL_MET_TASK_B)) == 0) {
VRT_fail(ctx, "shard .backend() method may only be used "
"in client and backend context");
return NULL;
}
assert(ctx->method & VCL_MET_TASK_B);
if (key_int && by != BY_KEY) {
shard_err(ctx, vshard->shardd,
"by=%s but key argument used", by_s);
return NULL;
}
if ((args & ~arg_resolve) == 0) {
/* no other parameters - shortcut */
AN(vshard->dir);
return (vshard->dir);
}
if (key_blob && by != BY_BLOB) {
shard_err(ctx, vshard->shardd,
"by=%s but key_blob argument used", by_s);
return NULL;
pp = shard_param_task(ctx, vshard, vshard->param);
if (pp == NULL)
return (NULL);
pp->vcl_name = vshard->shardd->name;
break;
case NOW:
if (ctx->method & VCL_MET_TASK_H) {
VRT_fail(ctx,
"shard .backend resolve=NOW can not be "
"used in vcl_init{}/vcl_fini{}");
return (NULL);
}
pp = shard_param_stack(&pstk, vshard->param,
vshard->shardd->name);
break;
default:
WRONG("resolve enum");
}
if (by == BY_BLOB) {
if (key_blob == NULL ||
key_blob->len <= 0 ||
key_blob->priv == NULL) {
shard_err0(ctx, vshard->shardd,
"by=BLOB but no or empty key_blob "
"- using key 0");
by = BY_KEY;
key_int = 0;
AN(pp);
if (args & arg_param) {
ppt = shard_param_blob(a->param);
if (ppt == NULL) {
VRT_fail(ctx, "shard .backend param invalid");
return (NULL);
}
pp->defaults = ppt;
}
key = get_key(ctx, by, key_int, key_blob);
pp = shard_param_args(ctx, pp, "shard.backend()",
args & _arg_mask_set,
a->by, a->key, a->key_blob, a->alt, a->warmup,
a->rampup, a->healthy);
if (pp == NULL)
return (NULL);
if (resolve == LAZY)
return (vshard->dir);
assert(resolve == NOW);
shard_param_merge(pp, pp->defaults);
return (sharddir_pick_be(ctx, vshard->shardd,
key, alt, warmup, rampup, healthy));
shard_get_key(ctx, pp), pp->alt, pp->warmup,
pp->rampup, pp->healthy));
}
static unsigned v_matchproto_(vdi_healthy)
vmod_shard_healthy(const struct director *dir, const struct busyobj *bo,
double *changed)
{
struct vmod_directors_shard *vshard;
CAST_OBJ_NOTNULL(vshard, dir->priv, VMOD_SHARD_SHARD_MAGIC);
return (sharddir_any_healthy(vshard->shardd, bo, changed));
}
static const struct director * v_matchproto_(vdi_resolve_f)
vmod_shard_resolve(const struct director *dir, struct worker *wrk,
struct busyobj *bo)
{
struct vmod_directors_shard *vshard;
struct vmod_directors_shard_param pstk[1];
const struct vmod_directors_shard_param *pp;
struct vrt_ctx ctx[1];
CHECK_OBJ_NOTNULL(dir, DIRECTOR_MAGIC);
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
CAST_OBJ_NOTNULL(vshard, dir->priv, VMOD_SHARD_SHARD_MAGIC);
// Ref: vcl_call_method()
INIT_OBJ(ctx, VRT_CTX_MAGIC);
ctx->vsl = bo->vsl;
ctx->vcl = bo->vcl;
ctx->http_bereq = bo->bereq;
ctx->http_beresp = bo->beresp;
ctx->bo = bo;
ctx->sp = bo->sp;
ctx->now = bo->t_prev;
ctx->ws = bo->ws;
ctx->method = VCL_MET_BACKEND_FETCH;
pp = vmod_shard_param_read(ctx, vshard,
vshard->param, pstk, "shard_resolve");
if (pp == NULL)
return (NULL);
return (sharddir_pick_be(ctx, vshard->shardd,
shard_get_key(ctx, pp), pp->alt, pp->warmup,
pp->rampup, pp->healthy));
}
VCL_VOID v_matchproto_(td_directors_shard_backend)
......@@ -293,3 +727,296 @@ vmod_shard_debug(VRT_CTX, struct vmod_directors_shard *vshard,
(void)ctx;
sharddir_debug(vshard->shardd, i & UINT32_MAX);
}
/* =============================================================
* shard_param
*/
VCL_VOID v_matchproto_(td_directors_shard_param__init)
vmod_shard_param__init(VRT_CTX,
struct vmod_directors_shard_param **pp, const char *vcl_name)
{
struct vmod_directors_shard_param *p;
(void) ctx;
AN(pp);
AZ(*pp);
ALLOC_OBJ(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
AN(p);
p->vcl_name = vcl_name;
p->scope = VCL;
p->defaults = &shard_param_default;
*pp = p;
}
VCL_VOID v_matchproto_(td_directors_shard_param__fini)
vmod_shard_param__fini(struct vmod_directors_shard_param **pp)
{
struct vmod_directors_shard_param *p = *pp;
if (p == NULL)
return;
*pp = NULL;
CHECK_OBJ_NOTNULL(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
FREE_OBJ(p);
}
/*
* init a stack param struct defaulting to pa with the given name
*/
static struct vmod_directors_shard_param *
shard_param_stack(struct vmod_directors_shard_param *p,
const struct vmod_directors_shard_param *pa, const char *who)
{
CHECK_OBJ_NOTNULL(pa, VMOD_SHARD_SHARD_PARAM_MAGIC);
assert(pa->scope > _SCOPE_INVALID);
AN(p);
INIT_OBJ(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
p->vcl_name = who;
p->scope = STACK;
p->defaults = pa;
return (p);
}
/*
* get a task scoped param struct for id defaulting to pa
* if id != pa and pa has VCL scope, also get a task scoped param struct for pa
*/
static struct vmod_directors_shard_param *
shard_param_task(VRT_CTX, const void *id,
const struct vmod_directors_shard_param *pa)
{
struct vmod_directors_shard_param *p;
struct vmod_priv *task;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(pa, VMOD_SHARD_SHARD_PARAM_MAGIC);
assert(pa->scope > _SCOPE_INVALID);
task = VRT_priv_task(ctx, id);
if (task == NULL) {
VRT_fail(ctx, "no priv_task");
return (NULL);
}
if (task->priv) {
p = task->priv;
CHECK_OBJ_NOTNULL(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
assert(p->scope == TASK);
/* XXX
VSL(SLT_Debug, 0,
"shard_param_task(id %p, pa %p) = %p (found, ws=%p)",
id, pa, p, ctx->ws);
*/
return (p);
}
p = WS_Alloc(ctx->ws, sizeof *p);
if (p == NULL) {
VRT_fail(ctx, "shard_param_task WS_Alloc failed");
return (NULL);
}
task->priv = p;
INIT_OBJ(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
p->vcl_name = pa->vcl_name;
p->scope = TASK;
if (id == pa || pa->scope != VCL)
p->defaults = pa;
else
p->defaults = shard_param_task(ctx, pa, pa);
/* XXX
VSL(SLT_Debug, 0,
"shard_param_task(id %p, pa %p) = %p (new, defaults = %p, ws=%p)",
id, pa, p, p->defaults, ctx->ws);
*/
return (p);
}
static struct vmod_directors_shard_param *
shard_param_prep(VRT_CTX, struct vmod_directors_shard_param *p,
const char *who)
{
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
if (ctx->method & VCL_MET_TASK_C) {
VRT_fail(ctx, "%s may only be used "
"in vcl_init and in backend context", who);
return (NULL);
} else if (ctx->method & VCL_MET_TASK_B)
p = shard_param_task(ctx, p, p);
else
assert(ctx->method & VCL_MET_TASK_H);
return (p);
}
VCL_VOID v_matchproto_(td_directors_shard_param_set)
vmod_shard_param_set(VRT_CTX, struct vmod_directors_shard_param *p,
struct vmod_shard_param_set_arg *a)
{
uint32_t args = shard_param_set_mask(a);
assert((args & ~_arg_mask_set) == 0);
p = shard_param_prep(ctx, p, "shard_param.set()");
if (p == NULL)
return;
(void) shard_param_args(ctx, p, "shard_param.set()", args,
a->by, a->key, a->key_blob, a->alt, a->warmup,
a->rampup, a->healthy);
}
VCL_VOID v_matchproto_(td_directors_shard_param_clear)
vmod_shard_param_clear(VRT_CTX,
struct vmod_directors_shard_param *p)
{
p = shard_param_prep(ctx, p, "shard_param.clear()");
if (p == NULL)
return;
p->mask = 0;
}
static const struct vmod_directors_shard_param *
vmod_shard_param_read(VRT_CTX, const void *id,
const struct vmod_directors_shard_param *p,
struct vmod_directors_shard_param *pstk, const char *who)
{
struct vmod_directors_shard_param *pp;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
(void) who; // XXX
if (ctx->method & VCL_MET_TASK_B)
p = shard_param_task(ctx, id, p);
if (p == NULL)
return (NULL);
pp = shard_param_stack(pstk, p, p->vcl_name);
AN(pp);
shard_param_merge(pp, p);
return (pp);
}
VCL_STRING v_matchproto_(td_directors_shard_param_get_by)
vmod_shard_param_get_by(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_directors_shard_param pstk;
const struct vmod_directors_shard_param *pp;
pp = vmod_shard_param_read(ctx, p, p, &pstk, "shard_param.get_by()");
if (pp == NULL)
return (NULL);
assert(pp->by > _BY_E_INVALID);
return (by_str[pp->by]);
}
VCL_INT v_matchproto_(td_directors_shard_param_get_key)
vmod_shard_param_get_key(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_directors_shard_param pstk;
const struct vmod_directors_shard_param *pp;
pp = vmod_shard_param_read(ctx, p, p, &pstk, "shard_param.get_key()");
if (pp == NULL)
return (-1);
return ((VCL_INT)shard_get_key(ctx, pp));
}
VCL_INT v_matchproto_(td_directors_shard_param_get_alt)
vmod_shard_param_get_alt(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_directors_shard_param pstk;
const struct vmod_directors_shard_param *pp;
pp = vmod_shard_param_read(ctx, p, p, &pstk,
"shard_param.get_alt()");
if (pp == NULL)
return (-1);
return (pp->alt);
}
VCL_REAL v_matchproto_(td_directors_shard_param_get_warmup)
vmod_shard_param_get_warmup(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_directors_shard_param pstk;
const struct vmod_directors_shard_param *pp;
pp = vmod_shard_param_read(ctx, p, p, &pstk,
"shard_param.get_warmup()");
if (pp == NULL)
return (-2);
return (pp->warmup);
}
VCL_BOOL v_matchproto_(td_directors_shard_param_get_rampup)
vmod_shard_param_get_rampup(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_directors_shard_param pstk;
const struct vmod_directors_shard_param *pp;
pp = vmod_shard_param_read(ctx, p, p, &pstk,
"shard_param.get_rampup()");
if (pp == NULL)
return (0);
return (pp->rampup);
}
VCL_STRING v_matchproto_(td_directors_shard_param_get_healthy)
vmod_shard_param_get_healthy(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_directors_shard_param pstk;
const struct vmod_directors_shard_param *pp;
pp = vmod_shard_param_read(ctx, p, p, &pstk,
"shard_param.get_healthy()");
if (pp == NULL)
return (NULL);
assert(pp->healthy > _HEALTHY_E_INVALID);
return (healthy_str[pp->healthy]);
}
static const struct vmod_directors_shard_param *
shard_param_blob(const VCL_BLOB blob)
{
if (blob && blob->priv &&
blob->len == sizeof(struct vmod_directors_shard_param) &&
*(unsigned *)blob->priv == VMOD_SHARD_SHARD_PARAM_MAGIC)
return (blob->priv);
return (NULL);
}
VCL_BLOB v_matchproto_(td_directors_shard_param_use)
vmod_shard_param_use(VRT_CTX,
struct vmod_directors_shard_param *p)
{
struct vmod_priv *blob;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(p, VMOD_SHARD_SHARD_PARAM_MAGIC);
blob = (void *)WS_Alloc(ctx->ws, sizeof *blob);
if (blob == NULL) {
VRT_fail(ctx, "Workspace overflow (param.use())");
return (NULL);
}
memset(blob, 0, sizeof *blob);
blob->len = sizeof *p;
blob->priv = p;
return (blob);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment