2 # 1. Traffic Server is able to start
3 # 2. Traffic Server spawns traffic_crashlog upon startup
4 # 3. Traffic Server proxies HTTP requests according to URL remapping rules
5 # in 'services.trafficserver.remap'
6 # 4. Traffic Server applies per-map settings specified with the conf_remap
8 # 5. Traffic Server caches HTTP responses
9 # 6. Traffic Server processes HTTP PUSH requests
10 # 7. Traffic Server can load the healthchecks plugin
11 # 8. Traffic Server logs HTTP traffic as configured
14 # - bin/traffic_manager
15 # - bin/traffic_server
16 # - bin/traffic_crashlog
17 # - bin/traffic_cache_tool
19 # - bin/traffic_logcat
20 # - bin/traffic_logstats
22 import ./make-test-python.nix ({ pkgs, ... }: {
23 name = "trafficserver";
24 meta = with pkgs.lib.maintainers; {
25 maintainers = [ midchildan ];
29 ats = { pkgs, lib, config, ... }: let
30 user = config.users.users.trafficserver.name;
31 group = config.users.groups.trafficserver.name;
32 healthchecks = pkgs.writeText "healthchecks.conf" ''
33 /status /tmp/ats.status text/plain 200 500
36 services.trafficserver.enable = true;
38 services.trafficserver.records = {
39 proxy.config.http.server_ports = "80 80:ipv6";
40 proxy.config.hostdb.host_file.path = "/etc/hosts";
41 proxy.config.log.max_space_mb_headroom = 0;
42 proxy.config.http.push_method_enabled = 1;
44 # check that cache storage is usable before accepting traffic
45 proxy.config.http.wait_for_cache = 2;
48 services.trafficserver.plugins = [
49 { path = "healthchecks.so"; arg = toString healthchecks; }
50 { path = "xdebug.so"; }
53 services.trafficserver.remap = ''
54 map http://httpbin.test http://httpbin
55 map http://pristine-host-hdr.test http://httpbin \
56 @plugin=conf_remap.so \
57 @pparam=proxy.config.url_remap.pristine_host_hdr=1
58 map http://ats/tspush http://httpbin/cache \
59 @plugin=conf_remap.so \
60 @pparam=proxy.config.http.cache.required_headers=0
63 services.trafficserver.storage = ''
67 networking.firewall.allowedTCPPorts = [ 80 ];
68 virtualisation.emptyDiskImages = [ 256 ];
69 services.udev.extraRules = ''
70 KERNEL=="vdb", OWNER="${user}", GROUP="${group}"
74 httpbin = { pkgs, lib, ... }: let
75 python = pkgs.python3.withPackages
76 (ps: with ps; [ httpbin gunicorn gevent ]);
78 systemd.services.httpbin = {
80 after = [ "network.target" ];
81 wantedBy = [ "multi-user.target" ];
83 ExecStart = "${python}/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent";
87 networking.firewall.allowedTCPPorts = [ 80 ];
90 client = { pkgs, lib, ... }: {
91 environment.systemPackages = with pkgs; [ curl ];
95 testScript = { nodes, ... }: let
96 sampleFile = pkgs.writeText "sample.txt" ''
97 It's the season of White Album.
103 ats.wait_for_unit("trafficserver")
104 ats.wait_for_open_port(80)
105 httpbin.wait_for_unit("httpbin")
106 httpbin.wait_for_open_port(80)
107 client.systemctl("start network-online.target")
108 client.wait_for_unit("network-online.target")
110 with subtest("Traffic Server is running"):
111 out = ats.succeed("traffic_ctl server status")
112 assert out.strip() == "Proxy -- on"
114 with subtest("traffic_crashlog is running"):
115 ats.succeed("pgrep -f traffic_crashlog")
117 with subtest("basic remapping works"):
118 out = client.succeed("curl -vv -H 'Host: httpbin.test' http://ats/headers")
119 assert json.loads(out)["headers"]["Host"] == "httpbin"
121 with subtest("conf_remap plugin works"):
122 out = client.succeed(
123 "curl -vv -H 'Host: pristine-host-hdr.test' http://ats/headers"
125 assert json.loads(out)["headers"]["Host"] == "pristine-host-hdr.test"
127 with subtest("caching works"):
128 out = client.succeed(
129 "curl -vv -D - -H 'Host: httpbin.test' -H 'X-Debug: X-Cache' http://ats/cache/60 -o /dev/null"
131 assert "X-Cache: miss" in out
133 out = client.succeed(
134 "curl -vv -D - -H 'Host: httpbin.test' -H 'X-Debug: X-Cache' http://ats/cache/60 -o /dev/null"
136 assert "X-Cache: hit-fresh" in out
138 with subtest("pushing to cache works"):
139 url = "http://ats/tspush"
141 ats.succeed(f"echo {url} > /tmp/urls.txt")
143 f"tspush -f '${sampleFile}' -u {url}"
145 assert "HTTP/1.0 201 Created" in out, "cache push failed"
148 "traffic_cache_tool --spans /etc/trafficserver/storage.config find --input /tmp/urls.txt"
150 assert "Span: /dev/vdb" in out, "cache not stored on disk"
152 out = client.succeed(f"curl {url}").strip()
154 open("${sampleFile}").read().strip()
156 assert out == expected, "cache content mismatch"
158 with subtest("healthcheck plugin works"):
159 out = client.succeed("curl -vv http://ats/status -o /dev/null -w '%{http_code}'")
160 assert out.strip() == "500"
162 ats.succeed("touch /tmp/ats.status")
164 out = client.succeed("curl -vv http://ats/status -o /dev/null -w '%{http_code}'")
165 assert out.strip() == "200"
167 with subtest("logging works"):
168 access_log_path = "/var/log/trafficserver/squid.blog"
169 ats.wait_for_file(access_log_path)
171 out = ats.succeed(f"traffic_logcat {access_log_path}").split("\n")[0]
172 expected = "^\S+ \S+ \S+ TCP_MISS/200 \S+ GET http://httpbin/headers - DIRECT/httpbin application/json$"
173 assert re.fullmatch(expected, out) is not None, "no matching logs"
175 out = json.loads(ats.succeed(f"traffic_logstats -jf {access_log_path}"))
176 assert isinstance(out, dict)
177 assert out["total"]["error.total"]["req"] == "0", "unexpected log stat"