Working with Varnish and writing VCL is just like any other development project: there’s code involved, and it will be deployed to a runtime that can be configured.
There are a lot of moving parts, so a quality assurance process should be in place.
This QA process ensures that the expectations are met, and that the deployed VCL results in the desired behavior.
There are a couple of ways to approach this. In this section we’ll discuss syntax validation and testing.
If you want check the syntactical correctness of your VCL code, you
can use the varnishadm vcl.load command. This command takes two
arguments: the name of the configuration and the VCL file:
~# varnishadm vcl.load myconfig default.vcl
VCL compiled.
When Varnish fails to compile your VCL file, the varnishadm
command will show you an error message:
~# varnishadm vcl.load myconfig default.vcl
Message from VCC-compiler:
Expected ';' got '}'
(program line 381), at
('/etc/varnish/default.vcl' Line 22 Pos 1)
}
#
Running VCC-compiler failed, exited with 2
VCL compilation failed
Command failed with error code 106
We casually use myconfig as the configuration name. Not only do we
advise you to figure out a more appropriate name, we want to warn you
that you’ll get the following error message when you try to load the
same configuration name twice:
~# varnishadm vcl.load myconfig default.vcl
Already a VCL named myconfig
Command failed with error code 106
After you’ve done your syntax checks using the vcl.load subcommand, we
advise you to discard the load configurations. Not only do you risk
name clashes, but loaded configurations also consume resources.
Here’s how you discard the myconfig VCL configuration:
varnishadm vcl.discard myconfig
You can also use varnishadm vcl.inline and pass a string containing
your VCL code. Here’s an example within the varnishadm shell:
varnish> vcl.inline myconfig2 << EOF
> vcl 4.1;
>
> backend default {
> .host = "localhost";
> .port = "8080";
>}
> EOF
200
VCL compiled.
You can also do the same from outside of the varnishadm shell, but
that requires two levels of HEREDOC:
In Bash, a Here document allows multiple lines to be passed as input to a command.
~# varnishadm << EOF
> vcl.inline myconfig2 << EOF2
> vcl 4.1;
> backend default {
>. .host = "localhost";
> .port = "8080";
> }
> EOF2
> EOF
200
VCL compiled.
A syntax check is good, but you can hardly consider it your only QA task. Logical errors aren’t detected. Testing the behavior of your VCL code is just as important.
The varnishtest program is a script-driven testing tool that uses
Varnish Test Case (VTC) files to test specific scenarios. It is a
functional testing tool, where the various elements of the delivery
chain can be defined:
The first example of a VTC file is the following example:
varnishtest "Check that a cache fetch + hit transaction works"
server s1 {
rxreq
txresp -hdr "Connection: close" -body "012345\n"
} -start
varnish v1 -vcl+backend { } -start
client c1 {
txreq -url "/"
rxresp
expect resp.status == 200
} -run
client c2 {
txreq -url "/"
rxresp
expect resp.status == 200
} -run
# Give varnish a chance to update stats
delay .1
varnish v1 -expect sess_conn == 2
varnish v1 -expect cache_hit == 1
varnish v1 -expect cache_miss == 1
varnish v1 -expect client_req == 2
This VTC file is executed using the following command:
~# varnishtest test.vtc
# top TEST test.vtc passed (5.446)
And as you can see, the test passed.
But what are we actually testing?
012345\n response body.If these expectations are met, the test passes.
Imagine the following VTC where we deliberately cause a failure:
varnishtest "A failing test"
server s1 {
rxreq
txresp -status 400
} -start
varnish v1 -vcl+backend { } -start
client c1 {
txreq
rxresp
expect resp.status == 200
} -run
The expectation is that the status code will be an HTTP 200. However, the server returns an HTTP 400 status code.
This is the command’s output:
~# /etc/varnish# varnishtest -q failed.vtc
# top TEST failed.vtc FAILED (5.245) exit=2
VTC files aren’t only there for your convenience and to test your implementation. The testing framework is also used by Varnish core developers for both the open source and the enterprise version of the software.
The master branch of Varnish Cache has about 850 VTC files in its GitHub repository. For Varnish Enterprise the number is around 1460.
The GitHub repository for Varnish Enterprise is not open source, of course, but you can definitely have a look at https://github.com/varnishcache/varnish-cache/tree/master/bin/varnishtest/tests to get inspired.
Here’s the content from the README file in that repository, which
explains the naming scheme that was used. If you understand the naming
scheme, you’ll be able to find the kind of test you’re looking for.
Naming scheme
-------------
The intent is to be able to run all scripts in lexicographic
order and get a sensible failure mode.
This requires more basic tests to be earlier and more complex
tests to be later in the test sequence, we do this with the
prefix/id letter:
[id]%05d.vtc
id ~ ^a --> varnishtest(1) tests
id ~ ^a02 --> HTTP2
id ~ ^b --> Basic functionality tests
id ~ ^c --> Complex functionality tests
id ~ ^d --> Director VMOD tests
id ~ ^e --> ESI tests
id ~ ^f --> Security-related tests
id ~ ^g --> GZIP tests
id ~ ^h --> HAproxy tests
id ~ ^i --> Interoperability and standards compliance
id ~ ^j --> JAIL tests
id ~ ^l --> VSL tests
id ~ ^m --> VMOD tests excluding director
id ~ ^o --> prOxy protocol
id ~ ^p --> Persistent tests
id ~ ^r --> Regression tests, same number as ticket
id ~ ^s --> Slow tests, expiry, grace, etc.
id ~ ^t --> Transport protocol tests
id ~ ^t02 --> HTTP2
id ~ ^u --> Utilities and background processes
id ~ ^v --> VCL tests: execute VRT functions
Remember that language cookie variation example in the Making changes section of this chapter?
To be sure the variations are respected by the VCL, we could run the following test:
varnishtest "Language cookie cache variation"
server s1 {
rxreq
expect req.http.Cookie == "language=nl"
txresp -body "Goede morgen"
rxreq
expect req.http.Cookie != "language=nl"
txresp -body "Good morning"
rxreq
expect req.http.Cookie != "language=nl"
txresp -body "Good morning"
rxreq
expect req.http.Cookie != "language=nl"
txresp -body "Good morning"
} -start
varnish v1 -vcl+backend {
vcl 4.1;
sub vcl_recv {
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(language)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.cookie ~ "^\s*$") {
unset req.http.cookie;
}
return(hash);
}
}
sub vcl_hash {
if(req.http.Cookie ~ "^.*language=(nl|en|fr);*.*$") {
hash_data(regsub( req.http.Cookie, "^.*language=(nl|en|fr);*.*$", "\1" ));
} else {
hash_data("en");
}
}
} -start
client c1 {
txreq -hdr "Cookie: a=1; b=2; language=nl; c=3"
rxresp
expect resp.body == "Goede morgen"
txreq -hdr "Cookie: a=1; language=en"
rxresp
expect resp.body == "Good morning"
txreq -hdr "Cookie: a=1; language=fr"
rxresp
expect resp.body == "Good morning"
txreq
rxresp
expect resp.body == "Good morning"
} -run
This example has a server that will return Goede morgen in Dutch
when the language cookie equals nl. In all other cases, we will fall
back to English and return Good morning.
The client will send requests containing various cookies: both
irrelevant cookies, and the relevant language cookie. Supported values
will be sent, like nl and en. We’ll also send an unsupported value,
like fr. And we’ll even test what happens if no Cookie header is
sent.
This test passes, so the VCL matches our expectations and correctly supports language cookie cache variations.