Fastly is a popular CDN based on the open-source Varnish. Since it supports VCL, a lot of custom “logic” to handle incoming requests can be added, right at the edge. This improves the user experience as well as reduces the overhead on origin servers.

Let’s take an example of redirects. Traditionally, they are applied on webservers and often involve not-so-straightforward syntax for complex rules (multiple checks for various headers, cookies, referrers, urls etc.). All of these are fairly straight forward to achieve in the VCL. Thanks to a lot of customization by Fastly on default VCL, you even have various functions OOTB for use. It makes redirects one of the easiest to offload to Fastly edge. This is what we will look at in this post.

The Problem

As the rules begin to increase on edge, you need to make changes reliably and also need to test before pushing them off to the live environment. You can create multiple services and deploy changes first on a lower environment, test it and then promote to live. As for automating these tests, you can use anything - even a simple curl command (have covered some of it here)

While curl maybe fine to test a few redirects, it’s not ideal if you want to run through hundreds of them, and inspect various different sections of the response. A lot of plumbing would be needed in shell to make it work. Ideally, we need programatic control and constructs.

The Solution

Enter behave. It is a BDD1 framework which uses tests written in a natural language style, backed up by Python code.

The tests can easily be executed in any CI/CD pipeline as an initial step of deploying a live service. The overall flow can be as follows with AWS Codepipeline and Codebuild - image

  1. Any commits on github triggers the AWS Codepipeline.
  2. First step in the pipeline deploys a test service with the same vcl.
  3. Second step runs the behave tests against this test service and destroy the temporary service afterwards.
  4. If the tests are successful, the pipeline then runs a tarraform plan against the live service.
  5. Waits for a manual approval. A member of the team can review the plan output and approve it.
  6. After approval, it deploys the new vcl to the live service.

I will cover the Fastly CI/CD in detail in a separate post later.

Let’s look at an example now.

Feature Test Cases

This defines the functionality that we need to implement and can be written by someone from the business, QA or PM. I am going to use the following structure and layout for this project.

+--tests/
    +--features/
    |    +-- steps/
    |    |    +-- steps.py
    |    +-- redirect.feature

Consider the first test. It has a feature redirect and within this, there are 2 scenarios. One, to test when actual users hit the site. Second, to test when some crawlers hit the site.

Feature: redirect

  Scenario: 
    Given I am a human
    And I visit https://fastly-bdd-example.com.global.prod.fastly.net/status/200
    Then Response is redirect
    And Response reason is REDIRECT
    And Status code is 302
    And Redirected url is https://fastly-bdd-example.com/gateway

  Scenario: 
    Given I am a googlebot
    And I visit https://fastly-bdd-example.com.global.prod.fastly.net/status/200
    Then Status code is 200
    And Response reason is OK

VCL

Now that we have the feature described above, we need to implement this using VCLs. We are going to use vcl snippets which are quicker to setup and use. These can be deployed by the CI/CD pipeline or if you are just testing, from the Fastly console as well.

recv vcl snippet

if (req.http.User-Agent ~ "googlebot"){
    return (lookup);
}

if (req.url ~ "^/status/200") {
    error 902;
}

error vcl snippet

if (obj.status == 902) {
    set obj.status = 302;
    set obj.response = "REDIRECT";
    set obj.http.Location = "https://" req.http.host "/gateway";
    return (deliver);
}

Step Files

The backend code for running the test is implemented in the file steps.py. You can split this across different files as your tests grow.

  
from behave import *
import requests

@given(u'I am a {useragent}')
def step_impl(context,useragent):
    if "bot" in useragent:
        context.useragent = useragent 
    else:
        context.useragent = "pybehave"

@given(u'I visit {url}')
def step_impl(context,url):
    headers = {'User-Agent': context.useragent}
    context.resp = requests.get(url, headers=headers, allow_redirects=False, verify=False )
    print("response_headers :"+str(context.resp.headers))

@then(u'Response is redirect')
def step_impl(context):
    assert context.resp.is_redirect

@then(u'Response reason is {resp_reason}')
def step_impl(context,resp_reason):
    print("response_reason : "+context.resp.reason)
    assert context.resp.reason == resp_reason

@then(u'Status code is {resp_status}')
def step_impl(context,resp_status):
    print("response_status : "+str(context.resp.status_code))
    assert context.resp.status_code == int(resp_status)

@then(u'Redirected url is {resp_url}')
def step_impl(context,resp_url):
    print("response_url : "+context.resp.headers['Location'])
    assert context.resp.headers['Location'] == resp_url

Executing Tests

Once you have the above files ready in your project, it is very simple to run the tests. You can use a lot of command line options as per your need. These are described in detail here

behave
.
.
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
6 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.051s

To generate test reports in the junit format use the --junit command-line option. The so generated reports can then be used by any existing junit visualization frameworks you have, to create visualizations.

Conclusion

Apart from the obvious benefits of BDD, this workflow ensures issues are caught early and the deployments are error free. A side benefit of deploying it via a CI/CD pipeline is that it also syntax checks the actual VCL snippets before they make it to the live service. Since Fastly’s vcl implementation is quite different from open source Varnish - it is near impossible to syntax check quickly using a varnish container.

Note: All the code mentioned in this post is here - https://github.com/abiydv/fastly-bdd-example

Cheers!

📝 This post was written by Abhinav

📌 Explore more Posts, Tags or Archives


📚 References:


  1. BDD or Behavior-driven development is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project ↩︎