[master] 9b8f6e2 shard director: LAZY mode (vdi resolve function), parameter objects

Nils Goroll nils.goroll at uplex.de
Mon Mar 5 21:47:06 UTC 2018


commit 9b8f6e21809bf867eb5bd792331484d0e70eb367
Author: Nils Goroll <nils.goroll at uplex.de>
Date:   Mon Mar 5 22:30:16 2018 +0100

    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

diff --git a/bin/varnishtest/tests/d00020.vtc b/bin/varnishtest/tests/d00020.vtc
new file mode 100644
index 0000000..358a623
--- /dev/null
+++ b/bin/varnishtest/tests/d00020.vtc
@@ -0,0 +1,412 @@
+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
diff --git a/bin/varnishtest/tests/d00021.vtc b/bin/varnishtest/tests/d00021.vtc
new file mode 100644
index 0000000..c29e03f
--- /dev/null
+++ b/bin/varnishtest/tests/d00021.vtc
@@ -0,0 +1,79 @@
+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
diff --git a/bin/varnishtest/tests/d00029.vtc b/bin/varnishtest/tests/d00029.vtc
new file mode 100644
index 0000000..93a1517
--- /dev/null
+++ b/bin/varnishtest/tests/d00029.vtc
@@ -0,0 +1,88 @@
+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
diff --git a/bin/varnishtest/tests/d00030.vtc b/bin/varnishtest/tests/d00030.vtc
new file mode 100644
index 0000000..202c485
--- /dev/null
+++ b/bin/varnishtest/tests/d00030.vtc
@@ -0,0 +1,186 @@
+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());
+    }
+}
diff --git a/doc/changes.rst b/doc/changes.rst
index e366f63..4d8fb92 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -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
 --------------------
 
diff --git a/lib/libvmod_directors/Makefile.am b/lib/libvmod_directors/Makefile.am
index 1275938..f4a59e2 100644
--- a/lib/libvmod_directors/Makefile.am
+++ b/lib/libvmod_directors/Makefile.am
@@ -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
diff --git a/lib/libvmod_directors/shard_dir.c b/lib/libvmod_directors/shard_dir.c
index ff5f9af..7a667c3 100644
--- a/lib/libvmod_directors/shard_dir.c
+++ b/lib/libvmod_directors/shard_dir.c
@@ -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,
diff --git a/lib/libvmod_directors/shard_dir.h b/lib/libvmod_directors/shard_dir.h
index 4df4151..f1b1b00 100644
--- a/lib/libvmod_directors/shard_dir.h
+++ b/lib/libvmod_directors/shard_dir.h
@@ -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);
 
diff --git a/lib/libvmod_directors/tbl_resolve.h b/lib/libvmod_directors/tbl_resolve.h
new file mode 100644
index 0000000..c7c6d82
--- /dev/null
+++ b/lib/libvmod_directors/tbl_resolve.h
@@ -0,0 +1,3 @@
+VMODENUM(NOW)
+VMODENUM(LAZY)
+#undef VMODENUM
diff --git a/lib/libvmod_directors/vmod.vcc b/lib/libvmod_directors/vmod.vcc
index ef2fd75..c2106a3 100644
--- a/lib/libvmod_directors/vmod.vcc
+++ b/lib/libvmod_directors/vmod.vcc
@@ -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 at 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.
diff --git a/lib/libvmod_directors/vmod_shard.c b/lib/libvmod_directors/vmod_shard.c
index 655e2ad..2fe2444 100644
--- a/lib/libvmod_directors/vmod_shard.c
+++ b/lib/libvmod_directors/vmod_shard.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright 2009-2016 UPLEX - Nils Goroll Systemoptimierung
+ * Copyright 2009-2018 UPLEX - Nils Goroll Systemoptimierung
  * All rights reserved.
  *
  * Authors: Julian Wiesener <jw at 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);
+}


More information about the varnish-commit mailing list