Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/e2e_tests/tests/pci_hotplug.rs
5394 views
1
// Copyright 2023 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Integration test for hotplug of tap devices as virtio-net.
6
7
#![cfg(all(unix, target_arch = "x86_64"))]
8
9
use std::net::Ipv4Addr;
10
use std::process::Command;
11
use std::thread;
12
use std::time::Duration;
13
use std::time::Instant;
14
15
use base::sys::linux::ioctl_with_val;
16
use base::test_utils::call_test_with_sudo;
17
use fixture::vm::Config;
18
use fixture::vm::TestVm;
19
use net_util::sys::linux::Tap;
20
use net_util::sys::linux::TapTLinux;
21
use net_util::MacAddress;
22
use net_util::TapTCommon;
23
24
/// Count the number of virtio-net devices.
25
fn count_virtio_net_devices(vm: &mut TestVm) -> usize {
26
let lspci_result = vm.exec_in_guest("lspci -n").unwrap();
27
// Count occurance for virtio net device: 1af4:1041
28
lspci_result.stdout.matches("1af4:1041").count()
29
}
30
31
/// Poll func until it returns true, or timeout is exceeded.
32
fn poll_until_true<F>(vm: &mut TestVm, func: F, timeout: Duration) -> bool
33
where
34
F: Fn(&mut TestVm) -> bool,
35
{
36
let poll_interval = Duration::from_millis(100);
37
let start_time = Instant::now();
38
while !func(vm) {
39
if start_time.elapsed() > timeout {
40
return false;
41
}
42
thread::sleep(poll_interval);
43
}
44
true
45
}
46
47
/// setup a tap device for test
48
fn setup_tap_device(tap_name: &[u8], ip_addr: Ipv4Addr, netmask: Ipv4Addr, mac_addr: MacAddress) {
49
let tap = Tap::new_with_name(tap_name, true, false).unwrap();
50
// SAFETY:
51
// ioctl is safe since we call it with a valid tap fd and check the return value.
52
let ret = unsafe { ioctl_with_val(&tap, net_sys::TUNSETPERSIST, 1) };
53
if ret < 0 {
54
panic!("Failed to persist tap interface");
55
}
56
tap.set_ip_addr(ip_addr).unwrap();
57
tap.set_netmask(netmask).unwrap();
58
tap.set_mac_address(mac_addr).unwrap();
59
tap.set_vnet_hdr_size(16).unwrap();
60
tap.set_offload(0).unwrap();
61
tap.enable().unwrap();
62
// Release tap to be used by the VM.
63
drop(tap);
64
}
65
66
/// Implementation for tap_hotplug_two
67
///
68
/// This test will fail by itself due to permission.
69
#[ignore = "Only to be called by tap_hotplug_two"]
70
#[test]
71
fn tap_hotplug_two_impl() {
72
let wait_timeout = Duration::from_secs(5);
73
// Setup VM start parameter.
74
let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "2".to_owned()]);
75
let mut vm = TestVm::new(config).unwrap();
76
77
//Setup test taps. tap_name has to be distinct per test, or it may appear flaky (b/333090169).
78
let tap1_name = "test_tap1";
79
setup_tap_device(
80
tap1_name.as_bytes(),
81
"100.115.92.15".parse().unwrap(),
82
"255.255.255.252".parse().unwrap(),
83
"a0:b0:c0:d0:e0:f1".parse().unwrap(),
84
);
85
let tap2_name = "test_tap2";
86
setup_tap_device(
87
tap2_name.as_bytes(),
88
"100.115.92.25".parse().unwrap(),
89
"255.255.255.252".parse().unwrap(),
90
"a0:b0:c0:d0:e0:f2".parse().unwrap(),
91
);
92
93
// Check number of virtio-net devices after each hotplug.
94
assert!(poll_until_true(
95
&mut vm,
96
|vm| { count_virtio_net_devices(vm) == 0 },
97
wait_timeout
98
));
99
vm.hotplug_tap(tap1_name).unwrap();
100
assert!(poll_until_true(
101
&mut vm,
102
|vm| { count_virtio_net_devices(vm) == 1 },
103
wait_timeout
104
));
105
vm.hotplug_tap(tap2_name).unwrap();
106
assert!(poll_until_true(
107
&mut vm,
108
|vm| { count_virtio_net_devices(vm) == 2 },
109
wait_timeout
110
));
111
112
// Check number of devices after each removal.
113
vm.remove_pci_device(1).unwrap();
114
assert!(poll_until_true(
115
&mut vm,
116
|vm| { count_virtio_net_devices(vm) == 1 },
117
wait_timeout
118
));
119
vm.remove_pci_device(2).unwrap();
120
assert!(poll_until_true(
121
&mut vm,
122
|vm| { count_virtio_net_devices(vm) == 0 },
123
wait_timeout
124
));
125
126
drop(vm);
127
Command::new("ip")
128
.args(["link", "delete", tap1_name])
129
.status()
130
.unwrap();
131
Command::new("ip")
132
.args(["link", "delete", tap2_name])
133
.status()
134
.unwrap();
135
}
136
137
/// Checks hotplug works with two tap devices.
138
#[test]
139
fn tap_hotplug_two() {
140
call_test_with_sudo("tap_hotplug_two_impl");
141
}
142
143
/// Implementation for tap_hotplug_add_remove_add
144
///
145
/// This test will fail by itself due to permission.
146
#[ignore = "Only to be called by tap_hotplug_add_remove_add"]
147
#[test]
148
fn tap_hotplug_add_remove_add_impl() {
149
let wait_timeout = Duration::from_secs(5);
150
// Setup VM start parameter.
151
let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "1".to_owned()]);
152
let mut vm = TestVm::new(config).unwrap();
153
154
//Setup test tap. tap_name has to be distinct per test, or it may appear flaky (b/333090169).
155
let tap_name = "test_tap3";
156
setup_tap_device(
157
tap_name.as_bytes(),
158
"100.115.92.5".parse().unwrap(),
159
"255.255.255.252".parse().unwrap(),
160
"a0:b0:c0:d0:e0:f0".parse().unwrap(),
161
);
162
163
assert!(poll_until_true(
164
&mut vm,
165
|vm| { count_virtio_net_devices(vm) == 0 },
166
wait_timeout
167
));
168
// Hotplug tap.
169
vm.hotplug_tap(tap_name).unwrap();
170
// Wait until virtio-net device appears in guest OS.
171
assert!(poll_until_true(
172
&mut vm,
173
|vm| { count_virtio_net_devices(vm) == 1 },
174
wait_timeout
175
));
176
177
// Remove hotplugged tap device.
178
vm.remove_pci_device(1).unwrap();
179
// Wait until virtio-net device disappears from guest OS.
180
assert!(poll_until_true(
181
&mut vm,
182
|vm| { count_virtio_net_devices(vm) == 0 },
183
wait_timeout
184
));
185
186
// Hotplug tap again.
187
vm.hotplug_tap(tap_name).unwrap();
188
// Wait until virtio-net device appears in guest OS.
189
assert!(poll_until_true(
190
&mut vm,
191
|vm| { count_virtio_net_devices(vm) == 1 },
192
wait_timeout
193
));
194
195
drop(vm);
196
Command::new("ip")
197
.args(["link", "delete", tap_name])
198
.status()
199
.unwrap();
200
}
201
202
/// Checks tap hotplug works with a device added, removed, then added again.
203
#[test]
204
fn tap_hotplug_add_remove_add() {
205
call_test_with_sudo("tap_hotplug_add_remove_add_impl");
206
}
207
208
/// Implementation for tap_hotplug_add_remove_rapid_add
209
///
210
/// This test will fail by itself due to permission.
211
#[ignore = "Only to be called by tap_hotplug_add_remove_rapid_add"]
212
#[test]
213
fn tap_hotplug_add_remove_rapid_add_impl() {
214
let wait_timeout = Duration::from_secs(5);
215
// Setup VM start parameter.
216
let config = Config::new().extra_args(vec!["--pci-hotplug-slots".to_owned(), "1".to_owned()]);
217
let mut vm = TestVm::new(config).unwrap();
218
219
//Setup test tap. tap_name has to be distinct per test, or it may appear flaky (b/333090169).
220
let tap_name_a = "test_tap4";
221
setup_tap_device(
222
tap_name_a.as_bytes(),
223
"100.115.92.9".parse().unwrap(),
224
"255.255.255.252".parse().unwrap(),
225
"a0:b0:c0:d0:e0:f0".parse().unwrap(),
226
);
227
228
let tap_name_b = "test_tap5";
229
setup_tap_device(
230
tap_name_b.as_bytes(),
231
"100.115.92.1".parse().unwrap(),
232
"255.255.255.252".parse().unwrap(),
233
"a0:b0:c0:d0:e0:f0".parse().unwrap(),
234
);
235
236
assert!(poll_until_true(
237
&mut vm,
238
|vm| { count_virtio_net_devices(vm) == 0 },
239
wait_timeout
240
));
241
// Hotplug tap.
242
vm.hotplug_tap(tap_name_a).unwrap();
243
// Wait until virtio-net device appears in guest OS.
244
assert!(poll_until_true(
245
&mut vm,
246
|vm| { count_virtio_net_devices(vm) == 1 },
247
wait_timeout
248
));
249
250
// Remove hotplugged tap device, then hotplug again without waiting for guest.
251
vm.remove_pci_device(1).unwrap();
252
vm.hotplug_tap(tap_name_b).unwrap();
253
254
// Wait for a while that the guest likely noticed the removal.
255
thread::sleep(Duration::from_millis(500));
256
// Wait until virtio-net device reappears in guest OS. This assertion would fail if the device
257
// added later is not recognized.
258
assert!(poll_until_true(
259
&mut vm,
260
|vm| { count_virtio_net_devices(vm) == 1 },
261
wait_timeout
262
));
263
264
drop(vm);
265
Command::new("ip")
266
.args(["link", "delete", tap_name_a])
267
.status()
268
.unwrap();
269
Command::new("ip")
270
.args(["link", "delete", tap_name_b])
271
.status()
272
.unwrap();
273
}
274
275
/// Checks tap hotplug works with a device added, removed, then rapidly added again.
276
#[test]
277
fn tap_hotplug_add_remove_rapid_add() {
278
call_test_with_sudo("tap_hotplug_add_remove_rapid_add_impl");
279
}
280
281