turnon: 1.6.1 -> 1.6.2 (#364647)
[NixPkgs.git] / nixos / tests / trafficserver.nix
blob9a64534e4357f4ffa2c2032c638a3a23d9c474d9
1 # verifies:
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
7 #      plugin
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
13 # uses:
14 #   - bin/traffic_manager
15 #   - bin/traffic_server
16 #   - bin/traffic_crashlog
17 #   - bin/traffic_cache_tool
18 #   - bin/traffic_ctl
19 #   - bin/traffic_logcat
20 #   - bin/traffic_logstats
21 #   - bin/tspush
22 import ./make-test-python.nix (
23   { pkgs, ... }:
24   {
25     name = "trafficserver";
26     meta = with pkgs.lib.maintainers; {
27       maintainers = [ midchildan ];
28     };
30     nodes = {
31       ats =
32         {
33           pkgs,
34           lib,
35           config,
36           ...
37         }:
38         let
39           user = config.users.users.trafficserver.name;
40           group = config.users.groups.trafficserver.name;
41           healthchecks = pkgs.writeText "healthchecks.conf" ''
42             /status /tmp/ats.status text/plain 200 500
43           '';
44         in
45         {
46           services.trafficserver.enable = true;
48           services.trafficserver.records = {
49             proxy.config.http.server_ports = "80 80:ipv6";
50             proxy.config.hostdb.host_file.path = "/etc/hosts";
51             proxy.config.log.max_space_mb_headroom = 0;
52             proxy.config.http.push_method_enabled = 1;
54             # check that cache storage is usable before accepting traffic
55             proxy.config.http.wait_for_cache = 2;
56           };
58           services.trafficserver.plugins = [
59             {
60               path = "healthchecks.so";
61               arg = toString healthchecks;
62             }
63             { path = "xdebug.so"; }
64           ];
66           services.trafficserver.remap = ''
67             map http://httpbin.test http://httpbin
68             map http://pristine-host-hdr.test http://httpbin \
69               @plugin=conf_remap.so \
70               @pparam=proxy.config.url_remap.pristine_host_hdr=1
71             map http://ats/tspush http://httpbin/cache \
72               @plugin=conf_remap.so \
73               @pparam=proxy.config.http.cache.required_headers=0
74           '';
76           services.trafficserver.storage = ''
77             /dev/vdb volume=1
78           '';
80           networking.firewall.allowedTCPPorts = [ 80 ];
81           virtualisation.emptyDiskImages = [ 256 ];
82           services.udev.extraRules = ''
83             KERNEL=="vdb", OWNER="${user}", GROUP="${group}"
84           '';
85         };
87       httpbin =
88         { pkgs, lib, ... }:
89         let
90           python = pkgs.python3.withPackages (
91             ps: with ps; [
92               httpbin
93               gunicorn
94               gevent
95             ]
96           );
97         in
98         {
99           systemd.services.httpbin = {
100             enable = true;
101             after = [ "network.target" ];
102             wantedBy = [ "multi-user.target" ];
103             serviceConfig = {
104               ExecStart = "${python}/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent";
105             };
106           };
108           networking.firewall.allowedTCPPorts = [ 80 ];
109         };
111       client =
112         { pkgs, lib, ... }:
113         {
114           environment.systemPackages = with pkgs; [ curl ];
115         };
116     };
118     testScript =
119       { nodes, ... }:
120       let
121         sampleFile = pkgs.writeText "sample.txt" ''
122           It's the season of White Album.
123         '';
124       in
125       ''
126         import json
127         import re
129         ats.wait_for_unit("trafficserver")
130         ats.wait_for_open_port(80)
131         httpbin.wait_for_unit("httpbin")
132         httpbin.wait_for_open_port(80)
133         client.systemctl("start network-online.target")
134         client.wait_for_unit("network-online.target")
136         with subtest("Traffic Server is running"):
137             out = ats.succeed("traffic_ctl server status")
138             assert out.strip() == "Proxy -- on"
140         with subtest("traffic_crashlog is running"):
141             ats.succeed("pgrep -f traffic_crashlog")
143         with subtest("basic remapping works"):
144             out = client.succeed("curl -vv -H 'Host: httpbin.test' http://ats/headers")
145             assert json.loads(out)["headers"]["Host"] == "httpbin"
147         with subtest("conf_remap plugin works"):
148             out = client.succeed(
149                 "curl -vv -H 'Host: pristine-host-hdr.test' http://ats/headers"
150             )
151             assert json.loads(out)["headers"]["Host"] == "pristine-host-hdr.test"
153         with subtest("caching works"):
154             out = client.succeed(
155                 "curl -vv -D - -H 'Host: httpbin.test' -H 'X-Debug: X-Cache' http://ats/cache/60 -o /dev/null"
156             )
157             assert "X-Cache: miss" in out
159             out = client.succeed(
160                 "curl -vv -D - -H 'Host: httpbin.test' -H 'X-Debug: X-Cache' http://ats/cache/60 -o /dev/null"
161             )
162             assert "X-Cache: hit-fresh" in out
164         with subtest("pushing to cache works"):
165             url = "http://ats/tspush"
167             ats.succeed(f"echo {url} > /tmp/urls.txt")
168             out = ats.succeed(
169                 f"tspush -f '${sampleFile}' -u {url}"
170             )
171             assert "HTTP/1.0 201 Created" in out, "cache push failed"
173             out = ats.succeed(
174                 "traffic_cache_tool --spans /etc/trafficserver/storage.config find --input /tmp/urls.txt"
175             )
176             assert "Span: /dev/vdb" in out, "cache not stored on disk"
178             out = client.succeed(f"curl {url}").strip()
179             expected = (
180                 open("${sampleFile}").read().strip()
181             )
182             assert out == expected, "cache content mismatch"
184         with subtest("healthcheck plugin works"):
185             out = client.succeed("curl -vv http://ats/status -o /dev/null -w '%{http_code}'")
186             assert out.strip() == "500"
188             ats.succeed("touch /tmp/ats.status")
190             out = client.succeed("curl -vv http://ats/status -o /dev/null -w '%{http_code}'")
191             assert out.strip() == "200"
193         with subtest("logging works"):
194             access_log_path = "/var/log/trafficserver/squid.blog"
195             ats.wait_for_file(access_log_path)
197             out = ats.succeed(f"traffic_logcat {access_log_path}").split("\n")[0]
198             expected = "^\S+ \S+ \S+ TCP_MISS/200 \S+ GET http://httpbin/headers - DIRECT/httpbin application/json$"
199             assert re.fullmatch(expected, out) is not None, "no matching logs"
201             out = json.loads(ats.succeed(f"traffic_logstats -jf {access_log_path}"))
202             assert isinstance(out, dict)
203             assert out["total"]["error.total"]["req"] == "0", "unexpected log stat"
204       '';
205   }