Search

Validation and testing

Validation and testing

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.

Syntax validation

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.

Testing

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 Varnish server
  • The client
  • The origin server
  • Extra proxy servers
  • Varnishlog output
  • Other server processes that are called

Built-in VCL test

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?

  • A server is defined that closes the connection after the request and returns the 012345\n response body.
  • A Varnish server is defined that connects with the backend.
  • The first client connects to the homepage and expects an HTTP 200 response.
  • The second client also connects to the homepage and expects an HTTP 200 response.
  • In the end the following expectations have to be met:
    • The total number of connections is two.
    • There was only one cache hit that occurred.
    • There was only one cache miss that occurred.
    • Two clients connect to Varnish in total.

If these expectations are met, the test passes.

A failing test

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

Looking at Varnish’s tests

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

A VCL test

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.


®Varnish Software, Wallingatan 12, 111 60 Stockholm, Organization nr. 556805-6203