mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-28 08:01:04 +00:00
vsock: Add support for multiple guests
Adds support for instantiating multiple `VhostUserVsockBackend`s parallely to handle multiple guests. Extends the CLI interface to accept the config for multiple VMs in addition to the yaml config file with the `--vm` argument as follows: vhost-user-vsock \ --vm guest_cid=3,socket=/tmp/vhost3.socket,uds_path=/tmp/vm3.vsock \ --vm guest_cid=4,socket=/tmp/vhost4.socket,uds_path=/tmp/vm4.vsock Signed-off-by: Priyansh Rathi <techiepriyansh@gmail.com>
This commit is contained in:
parent
f50e7147df
commit
a4aabb15e1
@ -41,20 +41,38 @@ the crate are split into various files as described below:
|
||||
Run the vhost-user-vsock device:
|
||||
```
|
||||
vhost-user-vsock --guest-cid=<CID assigned to the guest> \
|
||||
--socket=<path to the Unix socket to be created to communicate with the VMM via the vhost-user protocol>
|
||||
--uds-path=<path to the Unix socket to communicate with the guest via the virtio-vsock device>
|
||||
--socket=<path to the Unix socket to be created to communicate with the VMM via the vhost-user protocol> \
|
||||
--uds-path=<path to the Unix socket to communicate with the guest via the virtio-vsock device> \
|
||||
[--tx-buffer-size=<size of the buffer used for the TX virtqueue (guest->host packets)>]
|
||||
--config=<path to the local yaml configuration file>
|
||||
```
|
||||
or
|
||||
```
|
||||
vhost-user-vsock --vm guest_cid=<CID assigned to the guest>,socket=<path to the Unix socket to be created to communicate with the VMM via the vhost-user protocol>,uds-path=<path to the Unix socket to communicate with the guest via the virtio-vsock device>[,tx-buffer-size=<size of the buffer used for the TX virtqueue (guest->host packets)>]
|
||||
```
|
||||
|
||||
Configuration Example
|
||||
Specify the `--vm` argument multiple times to specify multiple devices like this:
|
||||
```
|
||||
vhost-user-vsock \
|
||||
--vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock \
|
||||
--vm guest-cid=4,socket=/tmp/vhost4.socket,uds-path=/tmp/vm4.vsock,tx-buffer-size=32768
|
||||
```
|
||||
|
||||
Or use a configuration file:
|
||||
```
|
||||
vhost-user-vsock --config=<path to the local yaml configuration file>
|
||||
```
|
||||
|
||||
Configuration file example:
|
||||
```yaml
|
||||
vms:
|
||||
- guest_cid: 3
|
||||
socket: /tmp/vhost3.socket
|
||||
uds_path: /tmp/vm3.sock
|
||||
tx_buffer_size: 65536
|
||||
- guest_cid: 4
|
||||
socket: /tmp/vhost4.socket
|
||||
uds_path: /tmp/vm4.sock
|
||||
tx_buffer_size: 32768
|
||||
```
|
||||
|
||||
Run VMM (e.g. QEMU):
|
||||
@ -71,11 +89,11 @@ qemu-system-x86_64 \
|
||||
## Working example
|
||||
|
||||
```sh
|
||||
shell1$ vhost-user-vsock --guest-cid=4 --uds-path=/tmp/vm4.vsock --socket=/tmp/vhost4.socket
|
||||
shell1$ vhost-user-vsock --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket
|
||||
```
|
||||
or if you want to configure the TX buffer size
|
||||
```sh
|
||||
shell1$ vhost-user-vsock --guest-cid=4 --uds-path=/tmp/vm4.vsock --socket=/tmp/vhost4.socket --tx-buffer-size=65536
|
||||
shell1$ vhost-user-vsock --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket,tx-buffer-size=65536
|
||||
```
|
||||
|
||||
```sh
|
||||
|
||||
@ -8,32 +8,61 @@ mod vhu_vsock;
|
||||
mod vhu_vsock_thread;
|
||||
mod vsock_conn;
|
||||
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
use std::{convert::TryFrom, sync::Arc, thread};
|
||||
|
||||
use crate::vhu_vsock::{Error, Result, VhostUserVsockBackend, VsockConfig};
|
||||
use crate::vhu_vsock::{VhostUserVsockBackend, VsockConfig};
|
||||
use clap::{Args, Parser};
|
||||
use log::{info, warn};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error as ThisError;
|
||||
use vhost::{vhost_user, vhost_user::Listener};
|
||||
use vhost_user_backend::VhostUserDaemon;
|
||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||
|
||||
#[derive(Args, Debug, Deserialize)]
|
||||
const DEFAULT_GUEST_CID: u64 = 3;
|
||||
const DEFAULT_TX_BUFFER_SIZE: u32 = 64 * 1024;
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
enum CliError {
|
||||
#[error("No arguments provided")]
|
||||
NoArgsProvided,
|
||||
#[error("Failed to parse configuration file")]
|
||||
ConfigParse,
|
||||
}
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
enum VmArgsParseError {
|
||||
#[error("Bad argument")]
|
||||
BadArgument,
|
||||
#[error("Invalid key `{0}`")]
|
||||
InvalidKey(String),
|
||||
#[error("Unable to convert string to integer: {0}")]
|
||||
ParseInteger(std::num::ParseIntError),
|
||||
#[error("Required key `{0}` not found")]
|
||||
RequiredKeyNotFound(String),
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug, Deserialize)]
|
||||
struct VsockParam {
|
||||
/// Context identifier of the guest which uniquely identifies the device for its lifetime.
|
||||
#[arg(long, default_value_t = 3, conflicts_with = "config")]
|
||||
#[arg(
|
||||
long,
|
||||
default_value_t = DEFAULT_GUEST_CID,
|
||||
conflicts_with = "config",
|
||||
conflicts_with = "vm"
|
||||
)]
|
||||
guest_cid: u64,
|
||||
|
||||
/// Unix socket to which a hypervisor connects to and sets up the control path with the device.
|
||||
#[arg(long, conflicts_with = "config")]
|
||||
#[arg(long, conflicts_with = "config", conflicts_with = "vm")]
|
||||
socket: String,
|
||||
|
||||
/// Unix socket to which a host-side application connects to.
|
||||
#[arg(long, conflicts_with = "config")]
|
||||
#[arg(long, conflicts_with = "config", conflicts_with = "vm")]
|
||||
uds_path: String,
|
||||
|
||||
/// The size of the buffer used for the TX virtqueue
|
||||
#[clap(long, default_value_t = 64 * 1024, conflicts_with = "config")]
|
||||
#[clap(long, default_value_t = DEFAULT_TX_BUFFER_SIZE, conflicts_with = "config", conflicts_with = "vm")]
|
||||
tx_buffer_size: u32,
|
||||
}
|
||||
|
||||
@ -43,45 +72,100 @@ struct VsockArgs {
|
||||
#[command(flatten)]
|
||||
param: Option<VsockParam>,
|
||||
|
||||
/// Device parameters corresponding to a VM in the form of comma separated key=value pairs.
|
||||
/// The allowed keys are: guest_cid, socket, uds_path and tx_buffer_size
|
||||
/// Example: --vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,tx-buffer-size=65536
|
||||
/// Multiple instances of this argument can be provided to configure devices for multiple guests.
|
||||
#[arg(long, conflicts_with = "config", verbatim_doc_comment, value_parser = parse_vm_params)]
|
||||
vm: Option<Vec<VsockConfig>>,
|
||||
|
||||
/// Load from a given configuration file
|
||||
#[arg(long)]
|
||||
config: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_vm_params(s: &str) -> Result<VsockConfig, VmArgsParseError> {
|
||||
let mut guest_cid = None;
|
||||
let mut socket = None;
|
||||
let mut uds_path = None;
|
||||
let mut tx_buffer_size = None;
|
||||
|
||||
for arg in s.trim().split(',') {
|
||||
let mut parts = arg.split('=');
|
||||
let key = parts.next().ok_or(VmArgsParseError::BadArgument)?;
|
||||
let val = parts.next().ok_or(VmArgsParseError::BadArgument)?;
|
||||
|
||||
match key {
|
||||
"guest_cid" | "guest-cid" => {
|
||||
guest_cid = Some(val.parse().map_err(VmArgsParseError::ParseInteger)?)
|
||||
}
|
||||
"socket" => socket = Some(val.to_string()),
|
||||
"uds_path" | "uds-path" => uds_path = Some(val.to_string()),
|
||||
"tx_buffer_size" | "tx-buffer-size" => {
|
||||
tx_buffer_size = Some(val.parse().map_err(VmArgsParseError::ParseInteger)?)
|
||||
}
|
||||
_ => return Err(VmArgsParseError::InvalidKey(key.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(VsockConfig::new(
|
||||
guest_cid.unwrap_or(DEFAULT_GUEST_CID),
|
||||
socket.ok_or_else(|| VmArgsParseError::RequiredKeyNotFound("socket".to_string()))?,
|
||||
uds_path.ok_or_else(|| VmArgsParseError::RequiredKeyNotFound("uds-path".to_string()))?,
|
||||
tx_buffer_size.unwrap_or(DEFAULT_TX_BUFFER_SIZE),
|
||||
))
|
||||
}
|
||||
|
||||
impl VsockArgs {
|
||||
pub fn parse_config(&self) -> Option<VsockConfig> {
|
||||
pub fn parse_config(&self) -> Option<Result<Vec<VsockConfig>, CliError>> {
|
||||
if let Some(c) = &self.config {
|
||||
let b = config::Config::builder()
|
||||
.add_source(config::File::new(c.as_str(), config::FileFormat::Yaml))
|
||||
.build();
|
||||
if let Ok(s) = b {
|
||||
let mut v = s.get::<Vec<VsockParam>>("vms").unwrap();
|
||||
if v.len() == 1 {
|
||||
return v.pop().map(|vm| {
|
||||
VsockConfig::new(vm.guest_cid, vm.socket, vm.uds_path, vm.tx_buffer_size)
|
||||
});
|
||||
if !v.is_empty() {
|
||||
let parsed: Vec<VsockConfig> = v
|
||||
.drain(..)
|
||||
.map(|p| {
|
||||
VsockConfig::new(
|
||||
p.guest_cid,
|
||||
p.socket.trim().to_string(),
|
||||
p.uds_path.trim().to_string(),
|
||||
p.tx_buffer_size,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
return Some(Ok(parsed));
|
||||
} else {
|
||||
return Some(Err(CliError::ConfigParse));
|
||||
}
|
||||
} else {
|
||||
return Some(Err(CliError::ConfigParse));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<VsockArgs> for VsockConfig {
|
||||
type Error = Error;
|
||||
impl TryFrom<VsockArgs> for Vec<VsockConfig> {
|
||||
type Error = CliError;
|
||||
|
||||
fn try_from(cmd_args: VsockArgs) -> Result<Self> {
|
||||
fn try_from(cmd_args: VsockArgs) -> Result<Self, CliError> {
|
||||
// we try to use the configuration first, if failed, then fall back to the manual settings.
|
||||
match cmd_args.parse_config() {
|
||||
Some(c) => Ok(c),
|
||||
_ => cmd_args.param.map_or(Err(Error::ConfigParse), |p| {
|
||||
Ok(Self::new(
|
||||
p.guest_cid,
|
||||
p.socket.trim().to_string(),
|
||||
p.uds_path.trim().to_string(),
|
||||
p.tx_buffer_size,
|
||||
))
|
||||
}),
|
||||
Some(c) => c,
|
||||
_ => match cmd_args.vm {
|
||||
Some(v) => Ok(v),
|
||||
_ => cmd_args.param.map_or(Err(CliError::NoArgsProvided), |p| {
|
||||
Ok(vec![VsockConfig::new(
|
||||
p.guest_cid,
|
||||
p.socket.trim().to_string(),
|
||||
p.uds_path.trim().to_string(),
|
||||
p.tx_buffer_size,
|
||||
)])
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,11 +213,39 @@ pub(crate) fn start_backend_server(config: VsockConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_backend_servers(configs: &[VsockConfig]) {
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for c in configs.iter() {
|
||||
let config = c.clone();
|
||||
let handle = thread::Builder::new()
|
||||
.name(format!("vhu-vsock-cid-{}", c.get_guest_cid()))
|
||||
.spawn(move || start_backend_server(config))
|
||||
.unwrap();
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let config = VsockConfig::try_from(VsockArgs::parse()).unwrap();
|
||||
start_backend_server(config);
|
||||
let mut configs = match Vec::<VsockConfig>::try_from(VsockArgs::parse()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
println!("Error parsing arguments: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if configs.len() == 1 {
|
||||
start_backend_server(configs.pop().unwrap());
|
||||
} else {
|
||||
start_backend_servers(&configs);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -152,12 +264,14 @@ mod tests {
|
||||
uds_path: uds_path.to_string(),
|
||||
tx_buffer_size,
|
||||
}),
|
||||
vm: None,
|
||||
config: None,
|
||||
}
|
||||
}
|
||||
fn from_file(config: &str) -> Self {
|
||||
VsockArgs {
|
||||
param: None,
|
||||
vm: None,
|
||||
config: Some(config.to_string()),
|
||||
}
|
||||
}
|
||||
@ -168,16 +282,56 @@ mod tests {
|
||||
fn test_vsock_config_setup() {
|
||||
let args = VsockArgs::from_args(3, "/tmp/vhost4.socket", "/tmp/vm4.vsock", 64 * 1024);
|
||||
|
||||
let config = VsockConfig::try_from(args);
|
||||
assert!(config.is_ok());
|
||||
let configs = Vec::<VsockConfig>::try_from(args);
|
||||
assert!(configs.is_ok());
|
||||
|
||||
let config = config.unwrap();
|
||||
let configs = configs.unwrap();
|
||||
assert_eq!(configs.len(), 1);
|
||||
|
||||
let config = &configs[0];
|
||||
assert_eq!(config.get_guest_cid(), 3);
|
||||
assert_eq!(config.get_socket_path(), "/tmp/vhost4.socket");
|
||||
assert_eq!(config.get_uds_path(), "/tmp/vm4.vsock");
|
||||
assert_eq!(config.get_tx_buffer_size(), 64 * 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_vsock_config_setup_from_vm_args() {
|
||||
let params = "--vm socket=/tmp/vhost3.socket,uds_path=/tmp/vm3.vsock \
|
||||
--vm socket=/tmp/vhost4.socket,uds-path=/tmp/vm4.vsock,guest-cid=4,tx_buffer_size=65536 \
|
||||
--vm guest-cid=5,socket=/tmp/vhost5.socket,uds_path=/tmp/vm5.vsock,tx-buffer-size=32768";
|
||||
|
||||
let mut params = params.split_whitespace().collect::<Vec<&str>>();
|
||||
params.insert(0, ""); // to make the test binary name agnostic
|
||||
|
||||
let args = VsockArgs::parse_from(params);
|
||||
|
||||
let configs = Vec::<VsockConfig>::try_from(args);
|
||||
assert!(configs.is_ok());
|
||||
|
||||
let configs = configs.unwrap();
|
||||
assert_eq!(configs.len(), 3);
|
||||
|
||||
let config = configs.get(0).unwrap();
|
||||
assert_eq!(config.get_guest_cid(), 3);
|
||||
assert_eq!(config.get_socket_path(), "/tmp/vhost3.socket");
|
||||
assert_eq!(config.get_uds_path(), "/tmp/vm3.vsock");
|
||||
assert_eq!(config.get_tx_buffer_size(), 65536);
|
||||
|
||||
let config = configs.get(1).unwrap();
|
||||
assert_eq!(config.get_guest_cid(), 4);
|
||||
assert_eq!(config.get_socket_path(), "/tmp/vhost4.socket");
|
||||
assert_eq!(config.get_uds_path(), "/tmp/vm4.vsock");
|
||||
assert_eq!(config.get_tx_buffer_size(), 65536);
|
||||
|
||||
let config = configs.get(2).unwrap();
|
||||
assert_eq!(config.get_guest_cid(), 5);
|
||||
assert_eq!(config.get_socket_path(), "/tmp/vhost5.socket");
|
||||
assert_eq!(config.get_uds_path(), "/tmp/vm5.vsock");
|
||||
assert_eq!(config.get_tx_buffer_size(), 32768);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_vsock_config_setup_from_file() {
|
||||
@ -191,7 +345,11 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let args = VsockArgs::from_file("./config.yaml");
|
||||
let config = VsockConfig::try_from(args).unwrap();
|
||||
|
||||
let configs = Vec::<VsockConfig>::try_from(args).unwrap();
|
||||
assert_eq!(configs.len(), 1);
|
||||
|
||||
let config = &configs[0];
|
||||
assert_eq!(config.get_guest_cid(), 4);
|
||||
assert_eq!(config.get_socket_path(), "/tmp/vhost4.socket");
|
||||
assert_eq!(config.get_uds_path(), "/tmp/vm4.vsock");
|
||||
|
||||
@ -123,8 +123,6 @@ pub(crate) enum Error {
|
||||
EmptyBackendRxQ,
|
||||
#[error("Failed to create an EventFd")]
|
||||
EventFdCreate(std::io::Error),
|
||||
#[error("Failed to parse a configuration file")]
|
||||
ConfigParse,
|
||||
}
|
||||
|
||||
impl std::convert::From<Error> for std::io::Error {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user