Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/integration_tests/functional/test_rate_limiter.py
1958 views
1
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Tests that fail if network throughput does not obey rate limits."""
4
import time
5
6
import framework.utils as utils
7
import host_tools.network as net_tools # pylint: disable=import-error
8
9
# The iperf version to run this tests with
10
IPERF_BINARY = 'iperf3'
11
12
# Interval used by iperf to get maximum bandwidth
13
IPERF_TRANSMIT_TIME = 4
14
15
# Use a fixed-size TCP window so we get constant flow
16
IPERF_TCP_WINDOW = '1000K'
17
18
# The rate limiting value
19
RATE_LIMIT_BYTES = 10485760
20
21
# The initial token bucket size
22
BURST_SIZE = 104857600
23
24
# The refill time for the token bucket
25
REFILL_TIME_MS = 100
26
27
# Deltas that are accepted between expected values and achieved
28
# values throughout the tests
29
MAX_BYTES_DIFF_PERCENTAGE = 10
30
MAX_TIME_DIFF = 25
31
32
33
def test_tx_rate_limiting(test_microvm_with_ssh, network_config):
34
"""Run iperf tx with and without rate limiting; check limiting effect."""
35
test_microvm = test_microvm_with_ssh
36
test_microvm.spawn()
37
38
test_microvm.basic_config()
39
40
# For this test we will be adding three interfaces:
41
# 1. No rate limiting
42
# 2. Rate limiting without burst
43
# 3. Rate limiting with burst
44
host_ips = ['', '', '']
45
guest_ips = ['', '', '']
46
47
iface_id = '1'
48
# Create tap before configuring interface.
49
_tap1, host_ip, guest_ip = test_microvm.ssh_network_config(
50
network_config,
51
iface_id
52
)
53
guest_ips[0] = guest_ip
54
host_ips[0] = host_ip
55
56
iface_id = '2'
57
tx_rate_limiter_no_burst = {
58
'bandwidth': {
59
'size': RATE_LIMIT_BYTES,
60
'refill_time': REFILL_TIME_MS
61
}
62
}
63
_tap2, host_ip, guest_ip = test_microvm.ssh_network_config(
64
network_config,
65
iface_id,
66
tx_rate_limiter=tx_rate_limiter_no_burst
67
)
68
guest_ips[1] = guest_ip
69
host_ips[1] = host_ip
70
71
iface_id = '3'
72
tx_rate_limiter_with_burst = {
73
'bandwidth': {
74
'size': RATE_LIMIT_BYTES,
75
'one_time_burst': BURST_SIZE,
76
'refill_time': REFILL_TIME_MS
77
}
78
}
79
_tap3, host_ip, guest_ip = test_microvm.ssh_network_config(
80
network_config,
81
iface_id,
82
tx_rate_limiter=tx_rate_limiter_with_burst
83
)
84
guest_ips[2] = guest_ip
85
host_ips[2] = host_ip
86
87
test_microvm.start()
88
89
_check_tx_rate_limiting(test_microvm, guest_ips, host_ips)
90
_check_tx_rate_limit_patch(test_microvm, guest_ips, host_ips)
91
92
93
def test_rx_rate_limiting(test_microvm_with_ssh, network_config):
94
"""Run iperf rx with and without rate limiting; check limiting effect."""
95
test_microvm = test_microvm_with_ssh
96
test_microvm.spawn()
97
98
test_microvm.basic_config()
99
100
# For this test we will be adding three interfaces:
101
# 1. No rate limiting
102
# 2. Rate limiting without burst
103
# 3. Rate limiting with burst
104
host_ips = ['', '', '']
105
guest_ips = ['', '', '']
106
107
iface_id = '1'
108
# Create tap before configuring interface.
109
_tap1, host_ip, guest_ip = test_microvm.ssh_network_config(
110
network_config,
111
iface_id
112
)
113
guest_ips[0] = guest_ip
114
host_ips[0] = host_ip
115
116
iface_id = '2'
117
rx_rate_limiter_no_burst = {
118
'bandwidth': {
119
'size': RATE_LIMIT_BYTES,
120
'refill_time': REFILL_TIME_MS
121
}
122
}
123
_tap2, host_ip, guest_ip = test_microvm.ssh_network_config(
124
network_config,
125
iface_id,
126
rx_rate_limiter=rx_rate_limiter_no_burst
127
)
128
guest_ips[1] = guest_ip
129
host_ips[1] = host_ip
130
131
iface_id = '3'
132
rx_rate_limiter_no_burst = {
133
'bandwidth': {
134
'size': RATE_LIMIT_BYTES,
135
'one_time_burst': BURST_SIZE,
136
'refill_time': REFILL_TIME_MS
137
}
138
}
139
_tap3, host_ip, guest_ip = test_microvm.ssh_network_config(
140
network_config,
141
iface_id,
142
rx_rate_limiter=rx_rate_limiter_no_burst
143
)
144
guest_ips[2] = guest_ip
145
host_ips[2] = host_ip
146
147
# Start the microvm.
148
test_microvm.start()
149
150
_check_rx_rate_limiting(test_microvm, guest_ips)
151
_check_rx_rate_limit_patch(test_microvm, guest_ips)
152
153
154
def test_rx_rate_limiting_cpu_load(test_microvm_with_ssh, network_config):
155
"""Run iperf rx with rate limiting; verify cpu load is below threshold."""
156
test_microvm = test_microvm_with_ssh
157
test_microvm.spawn()
158
159
test_microvm.basic_config()
160
161
# Enable monitor that checks if the cpu load is over the threshold.
162
# After multiple runs, the average value for the cpu load
163
# seems to be around 10%. Setting the threshold a little
164
# higher to skip false positives.
165
threshold = 20
166
test_microvm.enable_cpu_load_monitor(threshold)
167
168
# Create interface with aggressive rate limiting enabled.
169
rx_rate_limiter_no_burst = {
170
'bandwidth': {
171
'size': 65536, # 64KBytes
172
'refill_time': 1000 # 1s
173
}
174
}
175
_tap, _host_ip, guest_ip = test_microvm.ssh_network_config(
176
network_config,
177
'1',
178
rx_rate_limiter=rx_rate_limiter_no_burst
179
)
180
181
test_microvm.start()
182
183
# Start iperf server on guest.
184
_start_iperf_on_guest(test_microvm, guest_ip)
185
186
# Run iperf client sending UDP traffic.
187
iperf_cmd = '{} {} -u -c {} -b 1000000000 -t{} -f KBytes'.format(
188
test_microvm.jailer.netns_cmd_prefix(),
189
IPERF_BINARY,
190
guest_ip,
191
IPERF_TRANSMIT_TIME * 5
192
)
193
_iperf_out = _run_local_iperf(iperf_cmd)
194
195
196
def _check_tx_rate_limiting(test_microvm, guest_ips, host_ips):
197
"""Check that the transmit rate is within expectations."""
198
# Start iperf on the host as this is the tx rate limiting test.
199
_start_local_iperf(test_microvm.jailer.netns_cmd_prefix())
200
201
# First step: get the transfer rate when no rate limiting is enabled.
202
# We are receiving the result in KBytes from iperf.
203
print("Run guest TX iperf with no rate-limit")
204
rate_no_limit_kbps = _get_tx_bandwidth_with_duration(
205
test_microvm,
206
guest_ips[0],
207
host_ips[0],
208
IPERF_TRANSMIT_TIME
209
)
210
print("TX rate_no_limit_kbps: {}".format(rate_no_limit_kbps))
211
212
# Calculate the number of bytes that are expected to be sent
213
# in each second once the rate limiting is enabled.
214
expected_kbps = int(RATE_LIMIT_BYTES / (REFILL_TIME_MS / 1000.0) / 1024)
215
print("Rate-Limit TX expected_kbps: {}".format(expected_kbps))
216
217
# Sanity check that bandwidth with no rate limiting is at least double
218
# than the one expected when rate limiting is in place.
219
assert _get_percentage_difference(rate_no_limit_kbps, expected_kbps) > 100
220
221
# Second step: check bandwidth when rate limiting is on.
222
_check_tx_bandwidth(test_microvm, guest_ips[1], host_ips[1], expected_kbps)
223
224
# Third step: get the number of bytes when rate limiting is on and there is
225
# an initial burst size from where to consume.
226
print("Run guest TX iperf with exact burst size")
227
# Use iperf to obtain the bandwidth when there is burst to consume from,
228
# send exactly BURST_SIZE packets.
229
iperf_cmd = '{} -c {} -n {} -f KBytes -w {} -N'.format(
230
IPERF_BINARY,
231
host_ips[2],
232
BURST_SIZE,
233
IPERF_TCP_WINDOW
234
)
235
iperf_out = _run_iperf_on_guest(test_microvm, iperf_cmd, guest_ips[2])
236
print(iperf_out)
237
_, burst_kbps = _process_iperf_output(iperf_out)
238
print("TX burst_kbps: {}".format(burst_kbps))
239
# Test that the burst bandwidth is at least as two times the rate limit.
240
assert _get_percentage_difference(burst_kbps, expected_kbps) > 100
241
242
# Since the burst should be consumed, check rate limit is in place.
243
_check_tx_bandwidth(test_microvm, guest_ips[2], host_ips[2], expected_kbps)
244
245
246
def _check_rx_rate_limiting(test_microvm, guest_ips):
247
"""Check that the receiving rate is within expectations."""
248
# Start iperf on guest.
249
_start_iperf_on_guest(test_microvm, guest_ips[0])
250
251
# First step: get the transfer rate when no rate limiting is enabled.
252
# We are receiving the result in KBytes from iperf.
253
print("Run guest RX iperf with no rate-limit")
254
rate_no_limit_kbps = _get_rx_bandwidth_with_duration(
255
test_microvm,
256
guest_ips[0],
257
IPERF_TRANSMIT_TIME
258
)
259
print("RX rate_no_limit_kbps: {}".format(rate_no_limit_kbps))
260
261
# Calculate the number of bytes that are expected to be sent
262
# in each second once the rate limiting is enabled.
263
expected_kbps = int(RATE_LIMIT_BYTES / (REFILL_TIME_MS / 1000.0) / 1024)
264
print("Rate-Limit RX expected_kbps: {}".format(expected_kbps))
265
266
# Sanity check that bandwidth with no rate limiting is at least double
267
# than the one expected when rate limiting is in place.
268
assert _get_percentage_difference(rate_no_limit_kbps, expected_kbps) > 100
269
270
# Second step: check bandwidth when rate limiting is on.
271
_check_rx_bandwidth(test_microvm, guest_ips[1], expected_kbps)
272
273
# Third step: get the number of bytes when rate limiting is on and there is
274
# an initial burst size from where to consume.
275
print("Run guest TX iperf with exact burst size")
276
# Use iperf to obtain the bandwidth when there is burst to consume from,
277
# send exactly BURST_SIZE packets.
278
iperf_cmd = '{} {} -c {} -n {} -f KBytes -w {} -N'.format(
279
test_microvm.jailer.netns_cmd_prefix(),
280
IPERF_BINARY,
281
guest_ips[2],
282
BURST_SIZE,
283
IPERF_TCP_WINDOW
284
)
285
iperf_out = _run_local_iperf(iperf_cmd)
286
print(iperf_out)
287
_, burst_kbps = _process_iperf_output(iperf_out)
288
print("RX burst_kbps: {}".format(burst_kbps))
289
# Test that the burst bandwidth is at least as two times the rate limit.
290
assert _get_percentage_difference(burst_kbps, expected_kbps) > 100
291
292
# Since the burst should be consumed, check rate limit is in place.
293
_check_rx_bandwidth(test_microvm, guest_ips[2], expected_kbps)
294
295
296
def _check_tx_rate_limit_patch(test_microvm, guest_ips, host_ips):
297
"""Patch the TX rate limiters and check the new limits."""
298
bucket_size = int(RATE_LIMIT_BYTES * 2)
299
expected_kbps = int(bucket_size / (REFILL_TIME_MS / 1000.0) / 1024)
300
301
# Check that a TX rate limiter can be applied to a previously unlimited
302
# interface.
303
_patch_iface_bw(test_microvm, "1", "TX", bucket_size, REFILL_TIME_MS)
304
_check_tx_bandwidth(test_microvm, guest_ips[0], host_ips[0], expected_kbps)
305
306
# Check that a TX rate limiter can be updated.
307
_patch_iface_bw(test_microvm, "2", "TX", bucket_size, REFILL_TIME_MS)
308
_check_tx_bandwidth(test_microvm, guest_ips[1], host_ips[1], expected_kbps)
309
310
# Check that a TX rate limiter can be removed.
311
_patch_iface_bw(test_microvm, "1", "TX", 0, 0)
312
rate_no_limit_kbps = _get_tx_bandwidth_with_duration(
313
test_microvm,
314
guest_ips[0],
315
host_ips[0],
316
IPERF_TRANSMIT_TIME
317
)
318
# Check that bandwidth when rate-limit disabled is at least 1.5x larger
319
# than the one when rate limiting was enabled.
320
assert _get_percentage_difference(rate_no_limit_kbps, expected_kbps) > 50
321
322
323
def _check_rx_rate_limit_patch(test_microvm, guest_ips):
324
"""Patch the RX rate limiters and check the new limits."""
325
bucket_size = int(RATE_LIMIT_BYTES * 2)
326
expected_kbps = int(bucket_size / (REFILL_TIME_MS / 1000.0) / 1024)
327
328
# Check that an RX rate limiter can be applied to a previously unlimited
329
# interface.
330
_patch_iface_bw(test_microvm, "1", "RX", bucket_size, REFILL_TIME_MS)
331
_check_rx_bandwidth(test_microvm, guest_ips[0], expected_kbps)
332
333
# Check that an RX rate limiter can be updated.
334
_patch_iface_bw(test_microvm, "2", "RX", bucket_size, REFILL_TIME_MS)
335
_check_rx_bandwidth(test_microvm, guest_ips[1], expected_kbps)
336
337
# Check that an RX rate limiter can be removed.
338
_patch_iface_bw(test_microvm, "1", "RX", 0, 0)
339
rate_no_limit_kbps = _get_rx_bandwidth_with_duration(
340
test_microvm,
341
guest_ips[0],
342
IPERF_TRANSMIT_TIME
343
)
344
# Check that bandwidth when rate-limit disabled is at least 1.5x larger
345
# than the one when rate limiting was enabled.
346
assert _get_percentage_difference(rate_no_limit_kbps, expected_kbps) > 50
347
348
349
def _check_tx_bandwidth(
350
test_microvm,
351
guest_ip,
352
host_ip,
353
expected_kbps
354
):
355
"""Check that the rate-limited TX bandwidth is close to what we expect.
356
357
At this point, a daemonized iperf3 server is expected to be running on
358
the host.
359
"""
360
print("Check guest TX rate-limit; expected kbps {}".format(expected_kbps))
361
observed_kbps = _get_tx_bandwidth_with_duration(
362
test_microvm,
363
guest_ip,
364
host_ip,
365
IPERF_TRANSMIT_TIME
366
)
367
368
diff_pc = _get_percentage_difference(observed_kbps, expected_kbps)
369
print("TX calculated diff percentage: {}\n".format(diff_pc))
370
371
if diff_pc >= MAX_BYTES_DIFF_PERCENTAGE:
372
print("Short duration test failed. Try another run with 10x duration.")
373
374
observed_kbps = _get_tx_bandwidth_with_duration(
375
test_microvm,
376
guest_ip,
377
host_ip,
378
10 * IPERF_TRANSMIT_TIME
379
)
380
diff_pc = _get_percentage_difference(observed_kbps, expected_kbps)
381
print("TX calculated diff percentage: {}\n".format(diff_pc))
382
383
assert diff_pc < MAX_BYTES_DIFF_PERCENTAGE
384
385
386
def _get_tx_bandwidth_with_duration(
387
test_microvm,
388
guest_ip,
389
host_ip,
390
duration
391
):
392
"""Check that the rate-limited TX bandwidth is close to what we expect."""
393
iperf_cmd = '{} -c {} -t {} -f KBytes -w {} -N'.format(
394
IPERF_BINARY,
395
host_ip,
396
duration,
397
IPERF_TCP_WINDOW
398
)
399
400
iperf_out = _run_iperf_on_guest(test_microvm, iperf_cmd, guest_ip)
401
print(iperf_out)
402
403
_, observed_kbps = _process_iperf_output(iperf_out)
404
print("TX observed_kbps: {}".format(observed_kbps))
405
return observed_kbps
406
407
408
def _check_rx_bandwidth(
409
test_microvm,
410
guest_ip,
411
expected_kbps
412
):
413
"""Check that the rate-limited RX bandwidth is close to what we expect.
414
415
At this point, a daemonized iperf3 server is expected to be running on
416
the guest.
417
"""
418
print("Check guest RX rate-limit; expected kbps {}".format(expected_kbps))
419
observed_kbps = _get_rx_bandwidth_with_duration(
420
test_microvm,
421
guest_ip,
422
IPERF_TRANSMIT_TIME
423
)
424
425
diff_pc = _get_percentage_difference(observed_kbps, expected_kbps)
426
print("RX calculated diff percentage: {}\n".format(diff_pc))
427
428
if diff_pc >= MAX_BYTES_DIFF_PERCENTAGE:
429
print("Short duration test failed. Try another run with 10x duration.")
430
431
observed_kbps = _get_rx_bandwidth_with_duration(
432
test_microvm,
433
guest_ip,
434
10 * IPERF_TRANSMIT_TIME
435
)
436
diff_pc = _get_percentage_difference(observed_kbps, expected_kbps)
437
print("TX calculated diff percentage: {}\n".format(diff_pc))
438
439
assert diff_pc < MAX_BYTES_DIFF_PERCENTAGE
440
441
442
def _get_rx_bandwidth_with_duration(
443
test_microvm,
444
guest_ip,
445
duration
446
):
447
"""Check that the rate-limited RX bandwidth is close to what we expect."""
448
iperf_cmd = "{} {} -c {} -t {} -f KBytes -w {} -N".format(
449
test_microvm.jailer.netns_cmd_prefix(),
450
IPERF_BINARY,
451
guest_ip,
452
duration,
453
IPERF_TCP_WINDOW
454
)
455
iperf_out = _run_local_iperf(iperf_cmd)
456
print(iperf_out)
457
458
_, observed_kbps = _process_iperf_output(iperf_out)
459
print("RX observed_kbps: {}".format(observed_kbps))
460
return observed_kbps
461
462
463
def _patch_iface_bw(
464
test_microvm,
465
iface_id,
466
rx_or_tx,
467
new_bucket_size,
468
new_refill_time
469
):
470
"""Update the bandwidth rate limiter for a given interface.
471
472
Update the `rx_or_tx` rate limiter, on interface `iface_id` to the
473
new `bucket_size`.
474
"""
475
assert rx_or_tx in ['RX', 'TX']
476
args = {
477
'iface_id': iface_id,
478
"{}_rate_limiter".format(rx_or_tx.lower()): {
479
'bandwidth': {
480
'size': new_bucket_size,
481
'refill_time': new_refill_time
482
}
483
}
484
}
485
resp = test_microvm.network.patch(**args)
486
assert test_microvm.api_session.is_status_no_content(resp.status_code)
487
488
489
def _start_iperf_on_guest(test_microvm, hostname):
490
"""Start iperf in server mode through an SSH connection."""
491
test_microvm.ssh_config['hostname'] = hostname
492
ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
493
494
iperf_cmd = '{} -sD -f KBytes\n'.format(IPERF_BINARY)
495
ssh_connection.execute_command(iperf_cmd)
496
497
# Wait for the iperf daemon to start.
498
time.sleep(1)
499
500
501
def _run_iperf_on_guest(test_microvm, iperf_cmd, hostname):
502
"""Run a client related iperf command through an SSH connection."""
503
test_microvm.ssh_config['hostname'] = hostname
504
ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
505
_, stdout, stderr = ssh_connection.execute_command(iperf_cmd)
506
assert stderr.read() == ''
507
508
out = stdout.read()
509
return out
510
511
512
def _start_local_iperf(netns_cmd_prefix):
513
"""Start iperf in server mode after killing any leftover iperf daemon."""
514
iperf_cmd = 'pkill {}\n'.format(IPERF_BINARY)
515
516
# Don't check the result of this command because it can fail if no iperf
517
# is running.
518
utils.run_cmd(iperf_cmd, ignore_return_code=True)
519
520
iperf_cmd = '{} {} -sD -f KBytes\n'.format(netns_cmd_prefix, IPERF_BINARY)
521
522
utils.run_cmd(iperf_cmd)
523
524
# Wait for the iperf daemon to start.
525
time.sleep(1)
526
527
528
def _run_local_iperf(iperf_cmd):
529
"""Execute a client related iperf command locally."""
530
process = utils.run_cmd(iperf_cmd)
531
return process.stdout
532
533
534
def _get_percentage_difference(measured, base):
535
"""Return the percentage delta between the arguments."""
536
if measured == base:
537
return 0
538
try:
539
return (abs(measured - base) / base) * 100.0
540
except ZeroDivisionError:
541
# It means base and only base is 0.
542
return 100.0
543
544
545
def _process_iperf_line(line):
546
"""Parse iperf3 summary line and return test time and bandwidth."""
547
test_time = line.split(' ')[2].split('-')[1].strip().split(" ")[0]
548
test_bw = line.split(' ')[5].split(' ')[0].strip()
549
return float(test_time), float(test_bw)
550
551
552
def _process_iperf_output(iperf_out):
553
"""Parse iperf3 output and return average test time and bandwidth."""
554
iperf_out_lines = iperf_out.splitlines()
555
for line in iperf_out_lines:
556
if line.find('sender') != -1:
557
send_time, send_bw = _process_iperf_line(line)
558
if line.find('receiver') != -1:
559
rcv_time, rcv_bw = _process_iperf_line(line)
560
iperf_out_time = (send_time + rcv_time) / 2.0
561
iperf_out_bw = (send_bw + rcv_bw) / 2.0
562
return float(iperf_out_time), float(iperf_out_bw)
563
564