1
# Copyright (c) 2010 OpenStack, LLC.
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
7
# http://www.apache.org/licenses/LICENSE-2.0
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
17
import simplejson as json
21
from contextlib import contextmanager
23
from webob import Request, Response
25
from swift.common.middleware import staticweb
28
class FakeMemcache(object):
34
return self.store.get(key)
36
def set(self, key, value, timeout=0):
37
self.store[key] = value
40
def incr(self, key, timeout=0):
41
self.store[key] = self.store.setdefault(key, 0) + 1
42
return self.store[key]
45
def soft_lock(self, key, timeout=0, retries=5):
48
def delete(self, key):
56
class FakeApp(object):
58
def __init__(self, status_headers_body_iter=None):
59
self.get_c4_called = False
61
def __call__(self, env, start_response):
62
if env['PATH_INFO'] == '/':
63
return Response(status='404 Not Found')(env, start_response)
64
elif env['PATH_INFO'] == '/v1':
66
status='412 Precondition Failed')(env, start_response)
67
elif env['PATH_INFO'] == '/v1/a':
68
return Response(status='401 Unauthorized')(env, start_response)
69
elif env['PATH_INFO'] == '/v1/a/c1':
70
return Response(status='401 Unauthorized')(env, start_response)
71
elif env['PATH_INFO'] == '/v1/a/c2':
72
return self.listing(env, start_response,
73
{'x-container-read': '.r:*'})
74
elif env['PATH_INFO'] == '/v1/a/c2/one.txt':
75
return Response(status='404 Not Found')(env, start_response)
76
elif env['PATH_INFO'] == '/v1/a/c3':
77
return self.listing(env, start_response,
78
{'x-container-read': '.r:*',
79
'x-container-meta-web-index': 'index.html',
80
'x-container-meta-web-listings': 't'})
81
elif env['PATH_INFO'] == '/v1/a/c3/index.html':
82
return Response(status='200 Ok', body='''
85
<h1>Test main index.html file.</h1>
86
<p>Visit <a href="subdir">subdir</a>.</p>
87
<p>Don't visit <a href="subdir2/">subdir2</a> because it doesn't really
89
<p>Visit <a href="subdir3">subdir3</a>.</p>
90
<p>Visit <a href="subdir3/subsubdir">subdir3/subsubdir</a>.</p>
93
''')(env, start_response)
94
elif env['PATH_INFO'] == '/v1/a/c3/subdir':
95
return Response(status='404 Not Found')(env, start_response)
96
elif env['PATH_INFO'] == '/v1/a/c3/subdir/':
97
return Response(status='404 Not Found')(env, start_response)
98
elif env['PATH_INFO'] == '/v1/a/c3/subdir/index.html':
99
return Response(status='404 Not Found')(env, start_response)
100
elif env['PATH_INFO'] == '/v1/a/c3/subdir3/subsubdir':
101
return Response(status='404 Not Found')(env, start_response)
102
elif env['PATH_INFO'] == '/v1/a/c3/subdir3/subsubdir/':
103
return Response(status='404 Not Found')(env, start_response)
104
elif env['PATH_INFO'] == '/v1/a/c3/subdir3/subsubdir/index.html':
105
return Response(status='200 Ok', body='index file')(env,
107
elif env['PATH_INFO'] == '/v1/a/c3/subdirx/':
108
return Response(status='404 Not Found')(env, start_response)
109
elif env['PATH_INFO'] == '/v1/a/c3/subdirx/index.html':
110
return Response(status='404 Not Found')(env, start_response)
111
elif env['PATH_INFO'] == '/v1/a/c3/subdiry/':
112
return Response(status='404 Not Found')(env, start_response)
113
elif env['PATH_INFO'] == '/v1/a/c3/subdiry/index.html':
114
return Response(status='404 Not Found')(env, start_response)
115
elif env['PATH_INFO'] == '/v1/a/c3/subdirz':
116
return Response(status='404 Not Found')(env, start_response)
117
elif env['PATH_INFO'] == '/v1/a/c3/subdirz/index.html':
118
return Response(status='404 Not Found')(env, start_response)
119
elif env['PATH_INFO'] == '/v1/a/c3/unknown':
120
return Response(status='404 Not Found')(env, start_response)
121
elif env['PATH_INFO'] == '/v1/a/c3/unknown/index.html':
122
return Response(status='404 Not Found')(env, start_response)
123
elif env['PATH_INFO'] == '/v1/a/c4':
124
self.get_c4_called = True
125
return self.listing(env, start_response,
126
{'x-container-read': '.r:*',
127
'x-container-meta-web-index': 'index.html',
128
'x-container-meta-web-error': 'error.html',
129
'x-container-meta-web-listings': 't',
130
'x-container-meta-web-listings-css': 'listing.css'})
131
elif env['PATH_INFO'] == '/v1/a/c4/one.txt':
132
return Response(status='200 Ok', body='1')(env, start_response)
133
elif env['PATH_INFO'] == '/v1/a/c4/two.txt':
134
return Response(status='503 Service Unavailable')(env,
136
elif env['PATH_INFO'] == '/v1/a/c4/index.html':
137
return Response(status='404 Not Found')(env, start_response)
138
elif env['PATH_INFO'] == '/v1/a/c4/subdir/':
139
return Response(status='404 Not Found')(env, start_response)
140
elif env['PATH_INFO'] == '/v1/a/c4/subdir/index.html':
141
return Response(status='404 Not Found')(env, start_response)
142
elif env['PATH_INFO'] == '/v1/a/c4/unknown':
143
return Response(status='404 Not Found')(env, start_response)
144
elif env['PATH_INFO'] == '/v1/a/c4/unknown/index.html':
145
return Response(status='404 Not Found')(env, start_response)
146
elif env['PATH_INFO'] == '/v1/a/c4/404error.html':
147
return Response(status='200 Ok', body='''
149
<body style="background: #000000; color: #ffaaaa">
150
<p>Chrome's 404 fancy-page sucks.</p>
153
'''.strip())(env, start_response)
154
elif env['PATH_INFO'] == '/v1/a/c5':
155
return self.listing(env, start_response,
156
{'x-container-read': '.r:*',
157
'x-container-meta-web-index': 'index.html',
158
'x-container-meta-listings': 't',
159
'x-container-meta-web-error': 'error.html'})
160
elif env['PATH_INFO'] == '/v1/a/c5/index.html':
161
return Response(status='503 Service Unavailable')(env,
163
elif env['PATH_INFO'] == '/v1/a/c5/503error.html':
164
return Response(status='404 Not Found')(env, start_response)
165
elif env['PATH_INFO'] == '/v1/a/c5/unknown':
166
return Response(status='404 Not Found')(env, start_response)
167
elif env['PATH_INFO'] == '/v1/a/c5/unknown/index.html':
168
return Response(status='404 Not Found')(env, start_response)
169
elif env['PATH_INFO'] == '/v1/a/c5/404error.html':
170
return Response(status='404 Not Found')(env, start_response)
171
elif env['PATH_INFO'] == '/v1/a/c6':
172
return self.listing(env, start_response,
173
{'x-container-read': '.r:*',
174
'x-container-meta-web-listings': 't'})
175
elif env['PATH_INFO'] == '/v1/a/c6/subdir':
176
return Response(status='404 Not Found')(env, start_response)
177
elif env['PATH_INFO'] in ('/v1/a/c7', '/v1/a/c7/'):
178
return self.listing(env, start_response,
179
{'x-container-read': '.r:*',
180
'x-container-meta-web-listings': 'f'})
182
raise Exception('Unknown path %r' % env['PATH_INFO'])
184
def listing(self, env, start_response, headers):
185
if env['PATH_INFO'] in ('/v1/a/c3', '/v1/a/c4') and \
186
env['QUERY_STRING'] == 'delimiter=/&format=json&prefix=subdir/':
187
headers.update({'X-Container-Object-Count': '11',
188
'X-Container-Bytes-Used': '73741',
189
'X-Container-Read': '.r:*',
190
'Content-Type': 'application/json; charset=utf8'})
192
[{"name":"subdir/1.txt",
193
"hash":"5f595114a4b3077edfac792c61ca4fe4", "bytes":20,
194
"content_type":"text/plain",
195
"last_modified":"2011-03-24T04:27:52.709100"},
196
{"name":"subdir/2.txt",
197
"hash":"c85c1dcd19cf5cbac84e6043c31bb63e", "bytes":20,
198
"content_type":"text/plain",
199
"last_modified":"2011-03-24T04:27:52.734140"},
200
{"subdir":"subdir3/subsubdir/"}]
202
elif env['PATH_INFO'] == '/v1/a/c3' and env['QUERY_STRING'] == \
203
'delimiter=/&format=json&prefix=subdiry/':
204
headers.update({'X-Container-Object-Count': '11',
205
'X-Container-Bytes-Used': '73741',
206
'X-Container-Read': '.r:*',
207
'Content-Type': 'application/json; charset=utf8'})
209
elif env['PATH_INFO'] == '/v1/a/c3' and env['QUERY_STRING'] == \
210
'limit=1&format=json&delimiter=/&limit=1&prefix=subdirz/':
211
headers.update({'X-Container-Object-Count': '11',
212
'X-Container-Bytes-Used': '73741',
213
'X-Container-Read': '.r:*',
214
'Content-Type': 'application/json; charset=utf8'})
216
[{"name":"subdirz/1.txt",
217
"hash":"5f595114a4b3077edfac792c61ca4fe4", "bytes":20,
218
"content_type":"text/plain",
219
"last_modified":"2011-03-24T04:27:52.709100"}]
221
elif env['PATH_INFO'] == '/v1/a/c6' and env['QUERY_STRING'] == \
222
'limit=1&format=json&delimiter=/&limit=1&prefix=subdir/':
223
headers.update({'X-Container-Object-Count': '11',
224
'X-Container-Bytes-Used': '73741',
225
'X-Container-Read': '.r:*',
226
'X-Container-Web-Listings': 't',
227
'Content-Type': 'application/json; charset=utf8'})
229
[{"name":"subdir/1.txt",
230
"hash":"5f595114a4b3077edfac792c61ca4fe4", "bytes":20,
231
"content_type":"text/plain",
232
"last_modified":"2011-03-24T04:27:52.709100"}]
234
elif 'prefix=' in env['QUERY_STRING']:
235
return Response(status='204 No Content')
236
elif 'format=json' in env['QUERY_STRING']:
237
headers.update({'X-Container-Object-Count': '11',
238
'X-Container-Bytes-Used': '73741',
239
'Content-Type': 'application/json; charset=utf8'})
241
[{"name":"401error.html",
242
"hash":"893f8d80692a4d3875b45be8f152ad18", "bytes":110,
243
"content_type":"text/html",
244
"last_modified":"2011-03-24T04:27:52.713710"},
245
{"name":"404error.html",
246
"hash":"62dcec9c34ed2b347d94e6ca707aff8c", "bytes":130,
247
"content_type":"text/html",
248
"last_modified":"2011-03-24T04:27:52.720850"},
249
{"name":"index.html",
250
"hash":"8b469f2ca117668a5131fe9ee0815421", "bytes":347,
251
"content_type":"text/html",
252
"last_modified":"2011-03-24T04:27:52.683590"},
253
{"name":"listing.css",
254
"hash":"7eab5d169f3fcd06a08c130fa10c5236", "bytes":17,
255
"content_type":"text/css",
256
"last_modified":"2011-03-24T04:27:52.721610"},
257
{"name":"one.txt", "hash":"73f1dd69bacbf0847cc9cffa3c6b23a1",
258
"bytes":22, "content_type":"text/plain",
259
"last_modified":"2011-03-24T04:27:52.722270"},
260
{"name":"subdir/1.txt",
261
"hash":"5f595114a4b3077edfac792c61ca4fe4", "bytes":20,
262
"content_type":"text/plain",
263
"last_modified":"2011-03-24T04:27:52.709100"},
264
{"name":"subdir/2.txt",
265
"hash":"c85c1dcd19cf5cbac84e6043c31bb63e", "bytes":20,
266
"content_type":"text/plain",
267
"last_modified":"2011-03-24T04:27:52.734140"},
268
{"name":"subdir/omgomg.txt",
269
"hash":"7337d028c093130898d937c319cc9865", "bytes":72981,
270
"content_type":"text/plain",
271
"last_modified":"2011-03-24T04:27:52.735460"},
272
{"name":"subdir2", "hash":"d41d8cd98f00b204e9800998ecf8427e",
273
"bytes":0, "content_type":"text/directory",
274
"last_modified":"2011-03-24T04:27:52.676690"},
275
{"name":"subdir3/subsubdir/index.html",
276
"hash":"04eea67110f883b1a5c97eb44ccad08c", "bytes":72,
277
"content_type":"text/html",
278
"last_modified":"2011-03-24T04:27:52.751260"},
279
{"name":"two.txt", "hash":"10abb84c63a5cff379fdfd6385918833",
280
"bytes":22, "content_type":"text/plain",
281
"last_modified":"2011-03-24T04:27:52.825110"}]
284
headers.update({'X-Container-Object-Count': '11',
285
'X-Container-Bytes-Used': '73741',
286
'Content-Type': 'text/plain; charset=utf8'})
287
body = '\n'.join(['401error.html', '404error.html', 'index.html',
288
'listing.css', 'one.txt', 'subdir/1.txt',
289
'subdir/2.txt', 'subdir/omgomg.txt', 'subdir2',
290
'subdir3/subsubdir/index.html', 'two.txt'])
291
return Response(status='200 Ok', headers=headers,
292
body=body)(env, start_response)
295
class TestStaticWeb(unittest.TestCase):
298
self.test_staticweb = staticweb.filter_factory({})(FakeApp())
300
def test_app_set(self):
302
sw = staticweb.filter_factory({})(app)
303
self.assertEquals(sw.app, app)
305
def test_conf_set(self):
307
sw = staticweb.filter_factory(conf)(FakeApp())
308
self.assertEquals(sw.conf, conf)
310
def test_cache_timeout_unset(self):
311
sw = staticweb.filter_factory({})(FakeApp())
312
self.assertEquals(sw.cache_timeout, 300)
314
def test_cache_timeout_set(self):
315
sw = staticweb.filter_factory({'cache_timeout': '1'})(FakeApp())
316
self.assertEquals(sw.cache_timeout, 1)
319
resp = Request.blank('/').get_response(self.test_staticweb)
320
self.assertEquals(resp.status_int, 404)
322
def test_version(self):
323
resp = Request.blank('/v1').get_response(self.test_staticweb)
324
self.assertEquals(resp.status_int, 412)
326
def test_account(self):
327
resp = Request.blank('/v1/a').get_response(self.test_staticweb)
328
self.assertEquals(resp.status_int, 401)
330
def test_container1(self):
331
resp = Request.blank('/v1/a/c1').get_response(self.test_staticweb)
332
self.assertEquals(resp.status_int, 401)
334
def test_container2(self):
335
resp = Request.blank('/v1/a/c2').get_response(self.test_staticweb)
336
self.assertEquals(resp.status_int, 200)
337
self.assertEquals(resp.content_type, 'text/plain')
338
self.assertEquals(len(resp.body.split('\n')),
339
int(resp.headers['x-container-object-count']))
341
def test_container2onetxt(self):
342
resp = Request.blank(
343
'/v1/a/c2/one.txt').get_response(self.test_staticweb)
344
self.assertEquals(resp.status_int, 404)
346
def test_container2json(self):
347
resp = Request.blank(
348
'/v1/a/c2?format=json').get_response(self.test_staticweb)
349
self.assertEquals(resp.status_int, 200)
350
self.assertEquals(resp.content_type, 'application/json')
351
self.assertEquals(len(json.loads(resp.body)),
352
int(resp.headers['x-container-object-count']))
354
def test_container3(self):
355
resp = Request.blank('/v1/a/c3').get_response(self.test_staticweb)
356
self.assertEquals(resp.status_int, 301)
357
self.assertEquals(resp.headers['location'],
358
'http://localhost/v1/a/c3/')
360
def test_container3indexhtml(self):
361
resp = Request.blank('/v1/a/c3/').get_response(self.test_staticweb)
362
self.assertEquals(resp.status_int, 200)
363
self.assert_('Test main index.html file.' in resp.body)
365
def test_container3subdir(self):
366
resp = Request.blank(
367
'/v1/a/c3/subdir').get_response(self.test_staticweb)
368
self.assertEquals(resp.status_int, 301)
370
def test_container3subsubdir(self):
371
resp = Request.blank(
372
'/v1/a/c3/subdir3/subsubdir').get_response(self.test_staticweb)
373
self.assertEquals(resp.status_int, 301)
375
def test_container3subsubdircontents(self):
376
resp = Request.blank(
377
'/v1/a/c3/subdir3/subsubdir/').get_response(self.test_staticweb)
378
self.assertEquals(resp.status_int, 200)
379
self.assertEquals(resp.body, 'index file')
381
def test_container3subdir(self):
382
resp = Request.blank(
383
'/v1/a/c3/subdir/').get_response(self.test_staticweb)
384
self.assertEquals(resp.status_int, 200)
385
self.assert_('Listing of /v1/a/c3/subdir/' in resp.body)
386
self.assert_('</style>' in resp.body)
387
self.assert_('<link' not in resp.body)
388
self.assert_('listing.css' not in resp.body)
390
def test_container3subdirx(self):
391
resp = Request.blank(
392
'/v1/a/c3/subdirx/').get_response(self.test_staticweb)
393
self.assertEquals(resp.status_int, 404)
395
def test_container3subdiry(self):
396
resp = Request.blank(
397
'/v1/a/c3/subdiry/').get_response(self.test_staticweb)
398
self.assertEquals(resp.status_int, 404)
400
def test_container3subdirz(self):
401
resp = Request.blank(
402
'/v1/a/c3/subdirz').get_response(self.test_staticweb)
403
self.assertEquals(resp.status_int, 301)
405
def test_container3unknown(self):
406
resp = Request.blank(
407
'/v1/a/c3/unknown').get_response(self.test_staticweb)
408
self.assertEquals(resp.status_int, 404)
409
self.assert_("Chrome's 404 fancy-page sucks." not in resp.body)
411
def test_container4indexhtml(self):
412
resp = Request.blank('/v1/a/c4/').get_response(self.test_staticweb)
413
self.assertEquals(resp.status_int, 200)
414
self.assert_('Listing of /v1/a/c4/' in resp.body)
416
def test_container4indexhtmlauthed(self):
417
resp = Request.blank('/v1/a/c4').get_response(self.test_staticweb)
418
self.assertEquals(resp.status_int, 301)
419
resp = Request.blank('/v1/a/c4',
420
environ={'REMOTE_USER': 'authed'}).get_response(self.test_staticweb)
421
self.assertEquals(resp.status_int, 200)
422
resp = Request.blank('/v1/a/c4', headers={'x-web-mode': 't'},
423
environ={'REMOTE_USER': 'authed'}).get_response(self.test_staticweb)
424
self.assertEquals(resp.status_int, 301)
426
def test_container4unknown(self):
427
resp = Request.blank(
428
'/v1/a/c4/unknown').get_response(self.test_staticweb)
429
self.assertEquals(resp.status_int, 404)
430
self.assert_("Chrome's 404 fancy-page sucks." in resp.body)
432
def test_container4unknown_memcache(self):
433
fake_memcache = FakeMemcache()
434
self.assertEquals(fake_memcache.store, {})
435
resp = Request.blank('/v1/a/c4',
436
environ={'swift.cache': fake_memcache}
437
).get_response(self.test_staticweb)
438
self.assertEquals(resp.status_int, 301)
439
self.assertEquals(fake_memcache.store,
440
{'/staticweb/v1/a/c4':
441
('index.html', 'error.html', 't', 'listing.css')})
442
self.assert_(self.test_staticweb.app.get_c4_called)
443
self.test_staticweb.app.get_c4_called = False
444
resp = Request.blank('/v1/a/c4',
445
environ={'swift.cache': fake_memcache}
446
).get_response(self.test_staticweb)
447
self.assertEquals(resp.status_int, 301)
448
self.assert_(not self.test_staticweb.app.get_c4_called)
449
self.assertEquals(fake_memcache.store,
450
{'/staticweb/v1/a/c4':
451
('index.html', 'error.html', 't', 'listing.css')})
452
resp = Request.blank('/v1/a/c4',
453
environ={'swift.cache': fake_memcache, 'REQUEST_METHOD': 'PUT'}
454
).get_response(self.test_staticweb)
455
self.assertEquals(resp.status_int, 200)
456
self.assertEquals(fake_memcache.store, {})
457
resp = Request.blank('/v1/a/c4',
458
environ={'swift.cache': fake_memcache}
459
).get_response(self.test_staticweb)
460
self.assertEquals(resp.status_int, 301)
461
self.assertEquals(fake_memcache.store,
462
{'/staticweb/v1/a/c4':
463
('index.html', 'error.html', 't', 'listing.css')})
464
resp = Request.blank('/v1/a/c4',
465
environ={'swift.cache': fake_memcache, 'REQUEST_METHOD': 'POST'}
466
).get_response(self.test_staticweb)
467
self.assertEquals(resp.status_int, 200)
468
self.assertEquals(fake_memcache.store, {})
470
def test_container4subdir(self):
471
resp = Request.blank(
472
'/v1/a/c4/subdir/').get_response(self.test_staticweb)
473
self.assertEquals(resp.status_int, 200)
474
self.assert_('Listing of /v1/a/c4/subdir/' in resp.body)
475
self.assert_('</style>' not in resp.body)
476
self.assert_('<link' in resp.body)
477
self.assert_('listing.css' in resp.body)
479
def test_container4onetxt(self):
480
resp = Request.blank(
481
'/v1/a/c4/one.txt').get_response(self.test_staticweb)
482
self.assertEquals(resp.status_int, 200)
484
def test_container4twotxt(self):
485
resp = Request.blank(
486
'/v1/a/c4/two.txt').get_response(self.test_staticweb)
487
self.assertEquals(resp.status_int, 503)
489
def test_container5indexhtml(self):
490
resp = Request.blank('/v1/a/c5/').get_response(self.test_staticweb)
491
self.assertEquals(resp.status_int, 503)
493
def test_container5unknown(self):
494
resp = Request.blank(
495
'/v1/a/c5/unknown').get_response(self.test_staticweb)
496
self.assertEquals(resp.status_int, 404)
497
self.assert_("Chrome's 404 fancy-page sucks." not in resp.body)
499
def test_container6subdir(self):
500
resp = Request.blank(
501
'/v1/a/c6/subdir').get_response(self.test_staticweb)
502
self.assertEquals(resp.status_int, 301)
504
def test_container7listing(self):
505
resp = Request.blank('/v1/a/c7/').get_response(self.test_staticweb)
506
self.assertEquals(resp.status_int, 404)
509
if __name__ == '__main__':