~ttx/swift/release-1.4.2

« back to all changes in this revision

Viewing changes to test/unit/common/middleware/test_ratelimit.py

  • Committer: Tarmac
  • Author(s): gholt, FUJITA Tomonori, John Dickinson, David Goetz, John Dickinson, Joe Arnold, Scott Simpson, joe at cloudscaling, Thierry Carrez
  • Date: 2011-07-26 09:08:37 UTC
  • mfrom: (305.1.1 milestone-proposed)
  • Revision ID: tarmac-20110726090837-fwlvja8dnk7nkppw
Merge 1.4.2 development from trunk (rev331)

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
 
16
16
import unittest
17
17
import time
 
18
import eventlet
18
19
from contextlib import contextmanager
19
20
from threading import Thread
20
21
from webob import Request
30
31
    def __init__(self):
31
32
        self.store = {}
32
33
        self.error_on_incr = False
 
34
        self.init_incr_return_neg = False
33
35
 
34
36
    def get(self, key):
35
37
        return self.store.get(key)
41
43
    def incr(self, key, delta=1, timeout=0):
42
44
        if self.error_on_incr:
43
45
            raise MemcacheConnectionError('Memcache restarting')
 
46
        if self.init_incr_return_neg:
 
47
            # simulate initial hit, force reset of memcache
 
48
            self.init_incr_return_neg = False
 
49
            return -10000000
44
50
        self.store[key] = int(self.store.setdefault(key, 0)) + int(delta)
45
51
        if self.store[key] < 0:
46
52
            self.store[key] = 0
109
115
        return ratelimit.RateLimitMiddleware(app, conf, logger=FakeLogger())
110
116
    return limit_filter
111
117
 
 
118
time_ticker = 0
 
119
time_override = []
 
120
 
 
121
 
 
122
def mock_sleep(x):
 
123
    global time_ticker
 
124
    time_ticker += x
 
125
 
 
126
 
 
127
def mock_time():
 
128
    global time_override
 
129
    global time_ticker
 
130
    if time_override:
 
131
        cur_time = time_override.pop(0)
 
132
        if cur_time is None:
 
133
            time_override = [None if i is None else i + time_ticker
 
134
                             for i in time_override]
 
135
            return time_ticker
 
136
        return cur_time
 
137
    return time_ticker
 
138
 
112
139
 
113
140
class TestRateLimit(unittest.TestCase):
114
141
 
115
 
    def _run(self, callable_func, num, rate, extra_sleep=0,
116
 
             total_time=None, check_time=True):
 
142
    def _reset_time(self):
 
143
        global time_ticker
 
144
        time_ticker = 0
 
145
 
 
146
    def setUp(self):
 
147
        self.was_sleep = eventlet.sleep
 
148
        eventlet.sleep = mock_sleep
 
149
        self.was_time = time.time
 
150
        time.time = mock_time
 
151
        self._reset_time()
 
152
 
 
153
    def tearDown(self):
 
154
        eventlet.sleep = self.was_sleep
 
155
        time.time = self.was_time
 
156
 
 
157
    def _run(self, callable_func, num, rate, check_time=True):
 
158
        global time_ticker
117
159
        begin = time.time()
118
160
        for x in range(0, num):
119
161
            result = callable_func()
120
 
            # Extra sleep is here to test with different call intervals.
121
 
            time.sleep(extra_sleep)
122
162
        end = time.time()
123
 
        if total_time is None:
124
 
            total_time = num / rate
 
163
        total_time = float(num) / rate - 1.0 / rate # 1st request isn't limited
125
164
        # Allow for one second of variation in the total time.
126
165
        time_diff = abs(total_time - (end - begin))
127
166
        if check_time:
128
 
            self.assertTrue(time_diff < 1)
 
167
            self.assertEquals(round(total_time, 1), round(time_ticker, 1))
129
168
        return time_diff
130
169
 
131
170
    def test_get_container_maxrate(self):
150
189
                                                logger=FakeLogger())
151
190
        the_app.memcache_client = fake_memcache
152
191
        self.assertEquals(len(the_app.get_ratelimitable_key_tuples(
153
 
                    'GET', 'a', None, None)), 1)
154
 
        self.assertEquals(len(the_app.get_ratelimitable_key_tuples(
155
 
                    'POST', 'a', 'c', None)), 0)
 
192
                    'DELETE', 'a', None, None)), 0)
156
193
        self.assertEquals(len(the_app.get_ratelimitable_key_tuples(
157
194
                    'PUT', 'a', 'c', None)), 1)
158
195
        self.assertEquals(len(the_app.get_ratelimitable_key_tuples(
159
 
                    'GET', 'a', 'c', None)), 1)
 
196
                    'DELETE', 'a', 'c', None)), 1)
160
197
        self.assertEquals(len(the_app.get_ratelimitable_key_tuples(
161
198
                    'GET', 'a', 'c', 'o')), 0)
162
199
        self.assertEquals(len(the_app.get_ratelimitable_key_tuples(
163
200
                    'PUT', 'a', 'c', 'o')), 1)
164
201
 
165
 
    def test_ratelimit(self):
166
 
        current_rate = 13
167
 
        num_calls = 5
168
 
        conf_dict = {'account_ratelimit': current_rate}
169
 
        self.test_ratelimit = ratelimit.filter_factory(conf_dict)(FakeApp())
170
 
        ratelimit.http_connect = mock_http_connect(204)
171
 
        req = Request.blank('/v/a')
 
202
    def test_account_ratelimit(self):
 
203
        current_rate = 5
 
204
        num_calls = 50
 
205
        conf_dict = {'account_ratelimit': current_rate}
 
206
        self.test_ratelimit = ratelimit.filter_factory(conf_dict)(FakeApp())
 
207
        ratelimit.http_connect = mock_http_connect(204)
 
208
        for meth, exp_time in [('DELETE', 9.8), ('GET', 0),
 
209
                           ('POST', 0), ('PUT', 9.8)]:
 
210
            req = Request.blank('/v/a%s/c' % meth)
 
211
            req.method = meth
 
212
            req.environ['swift.cache'] = FakeMemcache()
 
213
            make_app_call = lambda: self.test_ratelimit(req.environ,
 
214
                                                        start_response)
 
215
            begin = time.time()
 
216
            self._run(make_app_call, num_calls, current_rate,
 
217
                      check_time=bool(exp_time))
 
218
            self.assertEquals(round(time.time() - begin, 1), exp_time)
 
219
            self._reset_time()
 
220
 
 
221
    def test_ratelimit_set_incr(self):
 
222
        current_rate = 5
 
223
        num_calls = 50
 
224
        conf_dict = {'account_ratelimit': current_rate}
 
225
        self.test_ratelimit = ratelimit.filter_factory(conf_dict)(FakeApp())
 
226
        ratelimit.http_connect = mock_http_connect(204)
 
227
        req = Request.blank('/v/a/c')
 
228
        req.method = 'PUT'
172
229
        req.environ['swift.cache'] = FakeMemcache()
 
230
        req.environ['swift.cache'].init_incr_return_neg = True
173
231
        make_app_call = lambda: self.test_ratelimit(req.environ,
174
232
                                                    start_response)
175
 
        self._run(make_app_call, num_calls, current_rate)
 
233
        begin = time.time()
 
234
        self._run(make_app_call, num_calls, current_rate, check_time=False)
 
235
        self.assertEquals(round(time.time() - begin, 1), 9.8)
176
236
 
177
237
    def test_ratelimit_whitelist(self):
 
238
        global time_ticker
178
239
        current_rate = 2
179
240
        conf_dict = {'account_ratelimit': current_rate,
180
241
                     'max_sleep_time_seconds': 2,
195
256
                self.result = self.parent.test_ratelimit(req.environ,
196
257
                                                         start_response)
197
258
        nt = 5
198
 
        begin = time.time()
199
259
        threads = []
200
260
        for i in range(nt):
201
261
            rc = rate_caller(self)
206
266
        the_498s = [t for t in threads if \
207
267
                        ''.join(t.result).startswith('Slow down')]
208
268
        self.assertEquals(len(the_498s), 0)
209
 
        time_took = time.time() - begin
210
 
        self.assert_(time_took < 1)
 
269
        self.assertEquals(time_ticker, 0)
211
270
 
212
271
    def test_ratelimit_blacklist(self):
 
272
        global time_ticker
213
273
        current_rate = 2
214
274
        conf_dict = {'account_ratelimit': current_rate,
215
275
                     'max_sleep_time_seconds': 2,
231
291
                self.result = self.parent.test_ratelimit(req.environ,
232
292
                                                         start_response)
233
293
        nt = 5
234
 
        begin = time.time()
235
294
        threads = []
236
295
        for i in range(nt):
237
296
            rc = rate_caller(self)
242
301
        the_497s = [t for t in threads if \
243
302
                        ''.join(t.result).startswith('Your account')]
244
303
        self.assertEquals(len(the_497s), 5)
245
 
        time_took = time.time() - begin
246
 
        self.assert_(round(time_took, 1) == 0)
 
304
        self.assertEquals(time_ticker, 0)
247
305
 
248
306
    def test_ratelimit_max_rate_double(self):
 
307
        global time_ticker
 
308
        global time_override
249
309
        current_rate = 2
250
310
        conf_dict = {'account_ratelimit': current_rate,
251
311
                     'clock_accuracy': 100,
252
312
                     'max_sleep_time_seconds': 1}
253
 
        # making clock less accurate for nosetests running slow
254
313
        self.test_ratelimit = dummy_filter_factory(conf_dict)(FakeApp())
255
314
        ratelimit.http_connect = mock_http_connect(204)
256
315
        self.test_ratelimit.log_sleep_time_seconds = .00001
257
 
        req = Request.blank('/v/a')
 
316
        req = Request.blank('/v/a/c')
 
317
        req.method = 'PUT'
258
318
        req.environ['swift.cache'] = FakeMemcache()
259
 
        begin = time.time()
260
 
 
261
 
        class rate_caller(Thread):
262
 
 
263
 
            def __init__(self, parent, name):
264
 
                Thread.__init__(self)
265
 
                self.parent = parent
266
 
                self.name = name
267
 
 
268
 
            def run(self):
269
 
                self.result1 = self.parent.test_ratelimit(req.environ,
270
 
                                                          start_response)
271
 
                time.sleep(.1)
272
 
                self.result2 = self.parent.test_ratelimit(req.environ,
273
 
                                                          start_response)
274
 
        nt = 3
275
 
        threads = []
276
 
        for i in range(nt):
277
 
            rc = rate_caller(self, "thread %s" % i)
278
 
            rc.start()
279
 
            threads.append(rc)
280
 
        for thread in threads:
281
 
            thread.join()
282
 
        all_results = [''.join(t.result1) for t in threads]
283
 
        all_results += [''.join(t.result2) for t in threads]
284
 
        the_498s = [t for t in all_results if t.startswith('Slow down')]
285
 
        self.assertEquals(len(the_498s), 2)
286
 
        time_took = time.time() - begin
287
 
        self.assert_(1.5 <= round(time_took, 1) < 1.7, time_took)
 
319
 
 
320
        time_override = [0, 0, 0, 0, None]
 
321
        # simulates 4 requests coming in at same time, then sleeping
 
322
        r = self.test_ratelimit(req.environ, start_response)
 
323
        mock_sleep(.1)
 
324
        r = self.test_ratelimit(req.environ, start_response)
 
325
        mock_sleep(.1)
 
326
        r = self.test_ratelimit(req.environ, start_response)
 
327
        self.assertEquals(r[0], 'Slow down')
 
328
        mock_sleep(.1)
 
329
        r = self.test_ratelimit(req.environ, start_response)
 
330
        self.assertEquals(r[0], 'Slow down')
 
331
        mock_sleep(.1)
 
332
        r = self.test_ratelimit(req.environ, start_response)
 
333
        self.assertEquals(r[0], '204 No Content')
288
334
 
289
335
    def test_ratelimit_max_rate_multiple_acc(self):
290
336
        num_calls = 4
297
343
                                                logger=FakeLogger())
298
344
        the_app.memcache_client = fake_memcache
299
345
        req = lambda: None
300
 
        req.method = 'GET'
 
346
        req.method = 'PUT'
301
347
 
302
348
        class rate_caller(Thread):
303
349
 
308
354
            def run(self):
309
355
                for j in range(num_calls):
310
356
                    self.result = the_app.handle_ratelimit(req, self.myname,
311
 
                                                           None, None)
 
357
                                                           'c', None)
312
358
 
313
359
        nt = 15
314
360
        begin = time.time()
319
365
            threads.append(rc)
320
366
        for thread in threads:
321
367
            thread.join()
322
 
        time_took = time.time() - begin
323
 
        # the all 15 threads still take 1.5 secs
324
 
        self.assert_(1.5 <= round(time_took, 1) < 1.7)
325
 
 
326
 
    def test_ratelimit_acc_vrs_container(self):
327
 
        conf_dict = {'clock_accuracy': 1000,
328
 
                     'account_ratelimit': 10,
329
 
                     'max_sleep_time_seconds': 4,
330
 
                     'container_ratelimit_10': 6,
331
 
                     'container_ratelimit_50': 2,
332
 
                     'container_ratelimit_75': 1}
333
 
        self.test_ratelimit = dummy_filter_factory(conf_dict)(FakeApp())
334
 
        ratelimit.http_connect = mock_http_connect(204)
335
 
        req = Request.blank('/v/a/c')
336
 
        req.environ['swift.cache'] = FakeMemcache()
337
 
        cont_key = get_container_memcache_key('a', 'c')
338
 
 
339
 
        class rate_caller(Thread):
340
 
 
341
 
            def __init__(self, parent, name):
342
 
                Thread.__init__(self)
343
 
                self.parent = parent
344
 
                self.name = name
345
 
 
346
 
            def run(self):
347
 
                self.result = self.parent.test_ratelimit(req.environ,
348
 
                                                         start_response)
349
 
 
350
 
        def runthreads(threads, nt):
351
 
            for i in range(nt):
352
 
                rc = rate_caller(self, "thread %s" % i)
353
 
                rc.start()
354
 
                threads.append(rc)
355
 
            for thread in threads:
356
 
                thread.join()
357
 
 
358
 
        begin = time.time()
359
 
        req.environ['swift.cache'].set(cont_key, {'container_size': 20})
360
 
        begin = time.time()
361
 
        threads = []
362
 
        runthreads(threads, 3)
363
 
        time_took = time.time() - begin
364
 
        self.assert_(round(time_took, 1) == .4)
 
368
 
 
369
        time_took = time.time() - begin
 
370
        self.assertEquals(1.5, round(time_took, 1))
365
371
 
366
372
    def test_call_invalid_path(self):
367
373
        env = {'REQUEST_METHOD': 'GET',
393
399
        req.environ['swift.cache'] = None
394
400
        make_app_call = lambda: self.test_ratelimit(req.environ,
395
401
                                                    start_response)
396
 
        self._run(make_app_call, num_calls, current_rate)
 
402
        begin = time.time()
 
403
        self._run(make_app_call, num_calls, current_rate, check_time=False)
 
404
        time_took = time.time() - begin
 
405
        self.assertEquals(round(time_took, 1), 0) # no memcache, no limiting
397
406
 
398
407
    def test_restarting_memcache(self):
399
408
        current_rate = 2
401
410
        conf_dict = {'account_ratelimit': current_rate}
402
411
        self.test_ratelimit = ratelimit.filter_factory(conf_dict)(FakeApp())
403
412
        ratelimit.http_connect = mock_http_connect(204)
404
 
        req = Request.blank('/v/a')
 
413
        req = Request.blank('/v/a/c')
 
414
        req.method = 'PUT'
405
415
        req.environ['swift.cache'] = FakeMemcache()
406
416
        req.environ['swift.cache'].error_on_incr = True
407
417
        make_app_call = lambda: self.test_ratelimit(req.environ,
409
419
        begin = time.time()
410
420
        self._run(make_app_call, num_calls, current_rate, check_time=False)
411
421
        time_took = time.time() - begin
412
 
        self.assert_(round(time_took, 1) == 0) # no memcache, no limiting
 
422
        self.assertEquals(round(time_took, 1), 0) # no memcache, no limiting
413
423
 
414
424
if __name__ == '__main__':
415
425
    unittest.main()