1'''
2'''
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#      http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing, software
14#  distributed under the License is distributed on an "AS IS" BASIS,
15#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16#  See the License for the specific language governing permissions and
17#  limitations under the License.
18
19Test.Summary = '''
20Basic cache_range_requests plugin test
21'''
22
23# Test description:
24# Preload the cache with the entire asset to be range requested.
25# Reload remap rule with cache_range_requests plugin
26# Request content through the cache_range_requests plugin
27
28Test.SkipUnless(
29    Condition.PluginExists('cache_range_requests.so'),
30    Condition.PluginExists('xdebug.so'),
31)
32Test.ContinueOnFail = False
33Test.testName = "cache_range_requests"
34
35# Define and configure ATS
36ts = Test.MakeATSProcess("ts", command="traffic_server")
37
38# Define and configure origin server
39server = Test.MakeOriginServer("server", lookup_key="{%uuid}")
40
41# default root
42req_chk = {"headers":
43           "GET / HTTP/1.1\r\n" +
44           "Host: www.example.com\r\n" +
45           "uuid: none\r\n" +
46           "\r\n",
47           "timestamp": "1469733493.993",
48           "body": ""
49           }
50
51res_chk = {"headers":
52           "HTTP/1.1 200 OK\r\n" +
53           "Connection: close\r\n" +
54           "\r\n",
55           "timestamp": "1469733493.993",
56           "body": ""
57           }
58
59server.addResponse("sessionlog.json", req_chk, res_chk)
60
61body = "lets go surfin now"
62
63req_full = {"headers":
64            "GET /path HTTP/1.1\r\n" +
65            "Host: www.example.com\r\n" +
66            "Accept: */*\r\n" +
67            "uuid: full\r\n" +
68            "\r\n",
69            "timestamp": "1469733493.993",
70            "body": ""
71            }
72
73res_full = {"headers":
74            "HTTP/1.1 200 OK\r\n" +
75            "Cache-Control: max-age=500\r\n" +
76            "Connection: close\r\n" +
77            'Etag: "path"\r\n' +
78            "\r\n",
79            "timestamp": "1469733493.993",
80            "body": body
81            }
82
83server.addResponse("sessionlog.json", req_full, res_full)
84
85block_bytes = 7
86bodylen = len(body)
87
88inner_str = "7-15"
89
90req_inner = {"headers":
91             "GET /path HTTP/1.1\r\n" +
92             "Host: www.example.com\r\n" +
93             "Accept: */*\r\n" +
94             "Range: bytes={}\r\n".format(inner_str) +
95             "uuid: inner\r\n" +
96             "\r\n",
97             "timestamp": "1469733493.993",
98             "body": ""
99             }
100
101res_inner = {"headers":
102             "HTTP/1.1 206 Partial Content\r\n" +
103             "Accept-Ranges: bytes\r\n" +
104             "Cache-Control: max-age=500\r\n" +
105             "Content-Range: bytes {0}/{1}\r\n".format(inner_str, bodylen) +
106             "Connection: close\r\n" +
107             'Etag: "path"\r\n' +
108             "\r\n",
109             "timestamp": "1469733493.993",
110             "body": body[7:15]
111             }
112
113server.addResponse("sessionlog.json", req_inner, res_inner)
114
115frange_str = "0-"
116
117req_frange = {"headers":
118              "GET /path HTTP/1.1\r\n" +
119              "Host: www.example.com\r\n" +
120              "Accept: */*\r\n" +
121              "Range: bytes={}\r\n".format(frange_str) +
122              "uuid: frange\r\n" +
123              "\r\n",
124              "timestamp": "1469733493.993",
125              "body": ""
126              }
127
128res_frange = {"headers":
129              "HTTP/1.1 206 Partial Content\r\n" +
130              "Accept-Ranges: bytes\r\n" +
131              "Cache-Control: max-age=500\r\n" +
132              "Content-Range: bytes 0-{0}/{0}\r\n".format(bodylen) +
133              "Connection: close\r\n" +
134              'Etag: "path"\r\n' +
135              "\r\n",
136              "timestamp": "1469733493.993",
137              "body": body
138              }
139
140server.addResponse("sessionlog.json", req_frange, res_frange)
141
142last_str = "-5"
143
144req_last = {"headers":
145            "GET /path HTTP/1.1\r\n" +
146            "Host: www.example.com\r\n" +
147            "Accept: */*\r\n" +
148            "Range: bytes={}\r\n".format(last_str) +
149            "uuid: last\r\n" +
150            "\r\n",
151            "timestamp": "1469733493.993",
152            "body": ""
153            }
154
155res_last = {"headers":
156            "HTTP/1.1 206 Partial Content\r\n" +
157            "Accept-Ranges: bytes\r\n" +
158            "Cache-Control: max-age=200\r\n" +
159            "Content-Range: bytes {0}-{1}/{1}\r\n".format(bodylen - 5, bodylen) +
160            "Connection: close\r\n" +
161            'Etag: "path"\r\n' +
162            "\r\n",
163            "timestamp": "1469733493.993",
164            "body": body[-5:]
165            }
166
167server.addResponse("sessionlog.json", req_last, res_last)
168
169pselect_str = "1-10"
170
171req_pselect = {"headers":
172               "GET /path HTTP/1.1\r\n" +
173               "Host: parentselect\r\n" +
174               "Accept: */*\r\n" +
175               "Range: bytes={}\r\n".format(pselect_str) +
176               "uuid: pselect\r\n" +
177               "\r\n",
178               "timestamp": "1469733493.993",
179               "body": ""
180               }
181
182res_pselect = {"headers":
183               "HTTP/1.1 206 Partial Content\r\n" +
184               "Accept-Ranges: bytes\r\n" +
185               "Cache-Control: max-age=200\r\n" +
186               "Content-Range: bytes {}/19\r\n".format(pselect_str) +
187               "Connection: close\r\n" +
188               'Etag: "path"\r\n' +
189               "\r\n",
190               "timestamp": "1469733493.993",
191               "body": body[1:10]
192               }
193
194server.addResponse("sessionlog.json", req_pselect, res_pselect)
195
196req_psd = {"headers":
197           "GET /path HTTP/1.1\r\n" +
198           "Host: psd\r\n" +
199           "Accept: */*\r\n" +
200           "Range: bytes={}\r\n".format(pselect_str) +
201           "uuid: pselect\r\n" +
202           "\r\n",
203           "timestamp": "1469733493.993",
204           "body": ""
205           }
206
207server.addResponse("sessionlog.json", req_psd, res_pselect)
208
209# cache range requests plugin remap
210ts.Disk.remap_config.AddLines([
211    'map http://www.example.com http://127.0.0.1:{}'.format(server.Variables.Port) +
212    ' @plugin=cache_range_requests.so',
213
214    # parent select cache key option
215    'map http://parentselect http://127.0.0.1:{}'.format(server.Variables.Port) +
216    ' @plugin=cache_range_requests.so @pparam=--ps-cachekey',
217
218    # deprecated
219    'map http://psd http://127.0.0.1:{}'.format(server.Variables.Port) +
220    ' @plugin=cache_range_requests.so @pparam=ps_mode:cache_key_url',
221])
222
223# cache debug
224ts.Disk.plugin_config.AddLine('xdebug.so')
225
226# minimal configuration
227ts.Disk.records_config.update({
228    'proxy.config.diags.debug.enabled': 1,
229    'proxy.config.diags.debug.tags': 'cache_range_requests',
230})
231
232curl_and_args = 'curl -s -D /dev/stdout -o /dev/stderr -x localhost:{} -H "x-debug: x-cache"'.format(ts.Variables.port)
233
234# 0 Test - Fetch whole asset into cache
235tr = Test.AddTestRun("full asset cache miss bypass")
236ps = tr.Processes.Default
237ps.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
238ps.StartBefore(Test.Processes.ts)
239ps.Command = curl_and_args + ' http://www.example.com/path -H "uuid: full"'
240ps.ReturnCode = 0
241ps.Streams.stderr = "gold/full.stderr.gold"
242tr.StillRunningAfter = ts
243
244# test inner range
245# 1 Test - Fetch range into cache
246tr = Test.AddTestRun("inner range cache miss")
247ps = tr.Processes.Default
248ps.Command = curl_and_args + ' http://www.example.com/path -r {} -H "uuid: inner"'.format(inner_str)
249ps.ReturnCode = 0
250ps.Streams.stderr = "gold/inner.stderr.gold"
251ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss")
252ps.Streams.stdout.Content += Testers.ContainsExpression("Content-Range: bytes 7-15/18", "expected content-range header")
253tr.StillRunningAfter = ts
254
255# 2 Test - Fetch from cache
256tr = Test.AddTestRun("inner range cache hit")
257ps = tr.Processes.Default
258ps.Command = curl_and_args + ' http://www.example.com/path -r {}'.format(inner_str)
259ps.ReturnCode = 0
260ps.Streams.stderr = "gold/inner.stderr.gold"
261ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit", "expected cache hit")
262ps.Streams.stdout.Content += Testers.ContainsExpression("Content-Range: bytes 7-15/18", "expected content-range header")
263tr.StillRunningAfter = ts
264
265# full range
266
267# 3 Test - 0- request
268tr = Test.AddTestRun("0- request miss")
269ps = tr.Processes.Default
270ps.Command = curl_and_args + ' http://www.example.com/path -r {} -H "uuid: frange"'.format(frange_str)
271ps.ReturnCode = 0
272ps.Streams.stderr = "gold/full.stderr.gold"
273ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss")
274ps.Streams.stdout.Content += Testers.ContainsExpression("Content-Range: bytes 0-18/18", "expected content-range header")
275tr.StillRunningAfter = ts
276
277# 4 Test - 0- request
278tr = Test.AddTestRun("0- request hit")
279ps = tr.Processes.Default
280ps.Command = curl_and_args + ' http://www.example.com/path -r {}'.format(frange_str)
281ps.ReturnCode = 0
282ps.Streams.stderr = "gold/full.stderr.gold"
283ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit", "expected cache hit")
284ps.Streams.stdout.Content += Testers.ContainsExpression("Content-Range: bytes 0-18/18", "expected content-range header")
285tr.StillRunningAfter = ts
286
287# end range
288
289# 5 Test - -5 request miss
290tr = Test.AddTestRun("-5 request miss")
291ps = tr.Processes.Default
292ps.Command = curl_and_args + ' http://www.example.com/path -r {} -H "uuid: last"'.format(last_str)
293ps.ReturnCode = 0
294ps.Streams.stderr = "gold/last.stderr.gold"
295ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss")
296ps.Streams.stdout.Content += Testers.ContainsExpression("Content-Range: bytes 13-18/18", "expected content-range header")
297tr.StillRunningAfter = ts
298
299# 6 Test - -5 request hit
300tr = Test.AddTestRun("-5 request hit")
301ps = tr.Processes.Default
302ps.Command = curl_and_args + ' http://www.example.com/path -r {}'.format(last_str)
303ps.ReturnCode = 0
304ps.Streams.stderr = "gold/last.stderr.gold"
305ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit", "expected cache hit")
306ps.Streams.stdout.Content += Testers.ContainsExpression("Content-Range: bytes 13-18/18", "expected content-range header")
307tr.StillRunningAfter = ts
308
309# Ensure 404's aren't getting cached
310
311# 7 Test - 404
312tr = Test.AddTestRun("404 request 1st")
313ps = tr.Processes.Default
314ps.Command = curl_and_args + ' http://www.example.com/404 -r 0-'
315ps.Streams.stdout = "gold/404.stdout.gold"
316ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss")
317tr.StillRunningAfter = ts
318
319# 8 Test - 404
320tr = Test.AddTestRun("404 request 2nd")
321ps = tr.Processes.Default
322ps.Command = curl_and_args + ' http://www.example.com/404 -r 0-'
323ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: miss", "expected cache miss")
324ps.Streams.stdout.Content += Testers.ContainsExpression("404 Not Found", "expected 404 response")
325
326tr.StillRunningAfter = ts
327
328curl_and_args = 'curl -s -D /dev/stdout -o /dev/stderr -x localhost:{} -H "x-debug: x-parentselection-key"'.format(
329    ts.Variables.port)
330
331# 9 Test - cache_key_url request
332tr = Test.AddTestRun("cache_key_url request")
333ps = tr.Processes.Default
334ps.Command = curl_and_args + ' http://parentselect/path -r {} -H "uuid: pselect"'.format(pselect_str)
335ps.ReturnCode = 0
336ps.Streams.stdout.Content = Testers.ContainsExpression(
337    "X-ParentSelection-Key: .*-bytes=",
338    "expected bytes in parent selection key",
339)
340tr.StillRunningAfter = ts
341tr.StillRunningAfter = server
342
343# 10 Test - non cache_key_url request ... no X-ParentSelection-Key
344tr = Test.AddTestRun("non cache_key_url request")
345ps = tr.Processes.Default
346ps.Command = curl_and_args + ' http://www.example.com/path -r {} -H "uuid: inner"'.format(inner_str)
347ps.ReturnCode = 0
348ps.Streams.stdout.Content = Testers.ExcludesExpression("X-ParentSelection-Key", "parent select key shouldn't show up")
349tr.StillRunningAfter = ts
350tr.StillRunningAfter = server
351
352# 11 Test - cache_key_url request -- deprecated
353tr = Test.AddTestRun("cache_key_url request - dprecated")
354ps = tr.Processes.Default
355ps.Command = curl_and_args + ' http://psd/path -r {} -H "uuid: pselect"'.format(pselect_str)
356ps.ReturnCode = 0
357ps.Streams.stdout.Content = Testers.ContainsExpression(
358    "X-ParentSelection-Key: .*-bytes=",
359    "expected bytes in parent selection key",
360)
361tr.StillRunningAfter = ts
362tr.StillRunningAfter = server
363