Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/e2e_tests/tests/console.rs
5394 views
1
// Copyright 2024 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
//! Testing virtio-console multiport feature.
6
7
#![cfg(any(target_os = "android", target_os = "linux"))]
8
9
use std::ffi::CString;
10
use std::fs::read_to_string;
11
use std::fs::OpenOptions;
12
use std::io::Read;
13
use std::io::Write;
14
use std::path::PathBuf;
15
16
use base::error;
17
use base::EventToken;
18
use base::WaitContext;
19
use base::WorkerThread;
20
use fixture::utils::create_vu_console_multiport_config;
21
use fixture::vhost_user::VhostUserBackend;
22
use fixture::vm::Config as VmConfig;
23
use fixture::vm::TestVm;
24
use tempfile::NamedTempFile;
25
use tempfile::TempDir;
26
27
fn run_vhost_user_console_multiport_test_portname(config: VmConfig) -> anyhow::Result<()> {
28
let socket = NamedTempFile::new().unwrap();
29
let temp_dir = TempDir::new()?;
30
31
// Prepare 2 virtio-console with only output
32
let file_path = vec![
33
(temp_dir.path().join("vconsole0.out"), PathBuf::new()),
34
(temp_dir.path().join("vconsole1.out"), PathBuf::new()),
35
];
36
let vu_config = create_vu_console_multiport_config(socket.path(), file_path.clone());
37
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
38
39
let config = config
40
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
41
.with_vhost_user("console", socket.path());
42
let mut vm = TestVm::new(config).unwrap();
43
44
// mount sysfs to check details
45
vm.exec_in_guest("mount -t sysfs sysfs /sys")?;
46
47
// Get portlist
48
let result = vm
49
.exec_in_guest("ls /sys/class/virtio-ports/")
50
.expect("No virtio-ports dir");
51
let mut portlist: Vec<&str> = result.stdout.trim_end().split('\n').collect();
52
// Remove serial virtio-console created defaultly
53
portlist.remove(0);
54
for (i, port) in portlist.into_iter().enumerate() {
55
let portname = vm
56
.exec_in_guest(format!("cat /sys/class/virtio-ports/{port}/name").as_str())
57
.expect("Failed to read portname")
58
.stdout;
59
assert_eq!(portname.trim_end(), format!("port{i}").as_str());
60
}
61
Ok(())
62
}
63
64
/// Tests vhost-user console device with `crosvm device`.
65
#[test]
66
fn vhost_user_console_portname_check() -> anyhow::Result<()> {
67
let config = VmConfig::new();
68
run_vhost_user_console_multiport_test_portname(config)?;
69
Ok(())
70
}
71
72
fn run_vhost_user_console_multiport_test_output(config: VmConfig) -> anyhow::Result<()> {
73
let socket = NamedTempFile::new().unwrap();
74
let temp_dir = TempDir::new()?;
75
76
// Prepare 2 virtio-console with only output
77
let file_path = vec![
78
(temp_dir.path().join("vconsole0.out"), PathBuf::new()),
79
(temp_dir.path().join("vconsole1.out"), PathBuf::new()),
80
];
81
let vu_config = create_vu_console_multiport_config(socket.path(), file_path.clone());
82
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
83
84
let config = config
85
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
86
.with_vhost_user("console", socket.path());
87
let mut vm = TestVm::new(config).unwrap();
88
89
// mount sysfs to check details
90
vm.exec_in_guest("mount -t sysfs sysfs /sys")?;
91
92
// Get portlist
93
let result = vm
94
.exec_in_guest("ls /sys/class/virtio-ports/")
95
.expect("No virtio-ports dir");
96
let mut portlist: Vec<&str> = result.stdout.trim_end().split('\n').collect();
97
// Remove serial virtio-console created defaultly
98
portlist.remove(0);
99
100
// Test output flow.
101
for (i, port) in portlist.into_iter().enumerate() {
102
vm.exec_in_guest(format!("echo \"hello {port}\" > /dev/{port}").as_str())
103
.expect("Failed to echo data to port");
104
105
let data = read_to_string(&file_path[i].0).expect("vu-console: read output failed");
106
107
assert_eq!(data.trim(), format!("hello {port}").as_str());
108
}
109
Ok(())
110
}
111
112
#[test]
113
fn vhost_user_console_check_output() -> anyhow::Result<()> {
114
let config = VmConfig::new();
115
run_vhost_user_console_multiport_test_output(config)?;
116
Ok(())
117
}
118
119
/// Generate the workthread to monitor input and transmit data to output fifo
120
///
121
/// Create fifo according to input and output name.
122
/// Then spawn a thread to monitor them, simultaneously watch a kill_event to stop thread.
123
fn generate_workthread_to_monitor_fifo(
124
idx: usize,
125
infile: PathBuf,
126
outfile: PathBuf,
127
) -> WorkerThread<()> {
128
#[derive(EventToken)]
129
enum Token {
130
InputDataAvailable,
131
Kill,
132
}
133
let cpath_in = CString::new(infile.to_str().unwrap()).unwrap();
134
let cpath_out = CString::new(outfile.to_str().unwrap()).unwrap();
135
// SAFETY: make two fifos here for monitor thread, path is guaranteed to be valid
136
unsafe {
137
libc::mkfifo(cpath_in.as_ptr(), 0o777);
138
libc::mkfifo(cpath_out.as_ptr(), 0o777);
139
}
140
WorkerThread::start(format!("monitor_vconsole{idx}"), move |kill_event| {
141
let mut tx = OpenOptions::new().write(true).open(outfile).unwrap();
142
let mut rx = OpenOptions::new().read(true).open(infile).unwrap();
143
let mut msg = vec![0; 256];
144
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
145
(&rx, Token::InputDataAvailable),
146
(&kill_event, Token::Kill),
147
]) {
148
Ok(wait_ctx) => wait_ctx,
149
Err(e) => {
150
error!("failed creating WaitContext: {}", e);
151
return;
152
}
153
};
154
'monitor_loop: loop {
155
let wait_events = match wait_ctx.wait() {
156
Ok(wait_events) => wait_events,
157
Err(e) => {
158
error!("failed polling for events: {}", e);
159
break;
160
}
161
};
162
for wait_event in wait_events.iter().filter(|e| e.is_readable) {
163
match wait_event.token {
164
Token::InputDataAvailable => {
165
let bytes = rx.read(&mut msg).expect("Failed to read from port");
166
if bytes > 0 {
167
if tx.write_all(&msg.to_ascii_uppercase()[..bytes]).is_err() {
168
break 'monitor_loop;
169
}
170
}
171
}
172
Token::Kill => break 'monitor_loop,
173
}
174
}
175
}
176
})
177
}
178
179
/// Tests vhost-user-console input function with multiport feature.
180
///
181
/// If we want to test multiport function about input flow,
182
/// we need to prepare monitor threads for each ports.
183
/// The purpose of this thread is to get all data from rx queue, and transmit them to tx queue.
184
/// To increase reliability, monitor thread changes data to uppercase.
185
///
186
/// Once monitor threads created, VhostUserBackend for console will work as expected.
187
fn run_vhost_user_console_multiport_test_input(config: VmConfig) -> anyhow::Result<()> {
188
let socket = NamedTempFile::new().unwrap();
189
let temp_dir = TempDir::new()?;
190
191
// Prepare 2 virtio-console with both input and output
192
let mut file_path = vec![];
193
for idx in 0..2 {
194
let fifo_name_out = format!("vconsole{idx}.out");
195
let fifo_name_in = format!("vconsole{idx}.in");
196
file_path.push((
197
temp_dir.path().join(fifo_name_out),
198
temp_dir.path().join(fifo_name_in),
199
));
200
}
201
202
let mut thread_vec = vec![];
203
for idx in 0..2 {
204
thread_vec.push(generate_workthread_to_monitor_fifo(
205
idx,
206
(*file_path.get(idx).unwrap().0).to_path_buf(),
207
(*file_path.get(idx).unwrap().1).to_path_buf(),
208
));
209
}
210
211
let vu_config = create_vu_console_multiport_config(socket.path(), file_path.clone());
212
let _vu_device = VhostUserBackend::new(vu_config).unwrap();
213
214
let config = config
215
.extra_args(vec!["--mem".to_owned(), "512".to_owned()])
216
.with_vhost_user("console", socket.path());
217
let mut vm = TestVm::new(config).unwrap();
218
219
// mount sysfs to check details
220
vm.exec_in_guest("mount -t sysfs sysfs /sys")?;
221
222
// Get portlist
223
let result = vm
224
.exec_in_guest("ls /sys/class/virtio-ports/")
225
.expect("No virtio-ports dir");
226
let mut portlist: Vec<&str> = result.stdout.trim_end().split('\n').collect();
227
// Remove serial virtio-console created defaultly
228
portlist.remove(0);
229
230
let file_fd = 5;
231
// Test input flow.
232
for port in portlist.into_iter() {
233
// Bind file_fd to operate /dev/vportXpX, then write to fd, finnally read it.
234
let result = vm
235
.exec_in_guest(
236
format!(
237
"exec {file_fd}<>/dev/{port} && echo \"hello {port}\" >&{file_fd} && head -1 <&{file_fd}"
238
)
239
.as_str(),
240
)
241
.expect("Failed to echo data to port")
242
.stdout;
243
// Close this fd
244
vm.exec_in_guest(format!("exec {file_fd}>&-").as_str())
245
.expect("Failed to close device fd");
246
// In monitor thread, tx message will change to uppercase
247
assert_eq!(
248
result.trim_end(),
249
format!("hello {port}").to_uppercase().as_str()
250
);
251
}
252
for handler in thread_vec.into_iter() {
253
handler.stop();
254
}
255
Ok(())
256
}
257
258
#[test]
259
fn vhost_user_console_check_input() -> anyhow::Result<()> {
260
let config = VmConfig::new();
261
run_vhost_user_console_multiport_test_input(config)?;
262
Ok(())
263
}
264
265