mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-19 20:24:35 +00:00
dragonball: add console manager.
Console manager to manage frontend and backend console devcies. A virtual console are composed up of two parts: frontend in virtual machine and backend in host OS. A frontend may be serial port, virtio-console etc, a backend may be stdio or Unix domain socket. The manager connects the frontend with the backend. Fixes: #4257 Signed-off-by: Liu Jiang <gerry@linux.alibaba.com> Signed-off-by: wllenyj <wllenyj@linux.alibaba.com> Signed-off-by: Chao Wu <chaowu@linux.alibaba.com>
This commit is contained in:
parent
3d38bb3005
commit
3c45c0715f
@ -11,10 +11,13 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
arc-swap = "1.5.0"
|
||||
bytes = "1.1.0"
|
||||
dbs-address-space = "0.1.0"
|
||||
dbs-allocator = "0.1.0"
|
||||
dbs-boot = "0.2.0"
|
||||
dbs-device = "0.1.0"
|
||||
dbs-legacy-devices = "0.1.0"
|
||||
dbs-utils = "0.1.0"
|
||||
kvm-bindings = "0.5.0"
|
||||
kvm-ioctls = "0.11.0"
|
||||
libc = "0.2.39"
|
||||
@ -29,5 +32,12 @@ thiserror = "1"
|
||||
vmm-sys-util = "0.9.0"
|
||||
vm-memory = { version = "0.7.0", features = ["backend-mmap"] }
|
||||
|
||||
[dev-dependencies]
|
||||
slog-term = "2.9.0"
|
||||
slog-async = "2.7.0"
|
||||
|
||||
[features]
|
||||
atomic-guest-memory = []
|
||||
|
||||
[patch.'crates-io']
|
||||
dbs-legacy-devices = { git = "https://github.com/openanolis/dragonball-sandbox.git", rev = "8e1181eca897b5c5e8e6ac45e3a5c995461865be" }
|
||||
|
430
src/dragonball/src/device_manager/console_manager.rs
Normal file
430
src/dragonball/src/device_manager/console_manager.rs
Normal file
@ -0,0 +1,430 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Virtual machine console device manager.
|
||||
//!
|
||||
//! A virtual console are composed up of two parts: frontend in virtual machine and backend in
|
||||
//! host OS. A frontend may be serial port, virtio-console etc, a backend may be stdio or Unix
|
||||
//! domain socket. The manager connects the frontend with the backend.
|
||||
use std::io::{self, Read};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use dbs_legacy_devices::{ConsoleHandler, SerialDevice};
|
||||
use dbs_utils::epoll_manager::{
|
||||
EpollManager, EventOps, EventSet, Events, MutEventSubscriber, SubscriberId,
|
||||
};
|
||||
use vmm_sys_util::terminal::Terminal;
|
||||
|
||||
use super::{DeviceMgrError, Result};
|
||||
|
||||
const EPOLL_EVENT_SERIAL: u32 = 0;
|
||||
const EPOLL_EVENT_SERIAL_DATA: u32 = 1;
|
||||
const EPOLL_EVENT_STDIN: u32 = 2;
|
||||
// Maximal backend throughput for every data transaction.
|
||||
const MAX_BACKEND_THROUGHPUT: usize = 64;
|
||||
|
||||
/// Errors related to Console manager operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConsoleManagerError {
|
||||
/// Cannot create unix domain socket for serial port
|
||||
#[error("cannot create socket for serial console")]
|
||||
CreateSerialSock(#[source] std::io::Error),
|
||||
|
||||
/// An operation on the epoll instance failed due to resource exhaustion or bad configuration.
|
||||
#[error("failure while managing epoll event for console fd")]
|
||||
EpollMgr(#[source] dbs_utils::epoll_manager::Error),
|
||||
|
||||
/// Cannot set mode for terminal.
|
||||
#[error("failure while setting attribute for terminal")]
|
||||
StdinHandle(#[source] vmm_sys_util::errno::Error),
|
||||
}
|
||||
|
||||
enum Backend {
|
||||
StdinHandle(std::io::Stdin),
|
||||
SockPath(String),
|
||||
}
|
||||
|
||||
/// Console manager to manage frontend and backend console devices.
|
||||
pub struct ConsoleManager {
|
||||
epoll_mgr: EpollManager,
|
||||
logger: slog::Logger,
|
||||
subscriber_id: Option<SubscriberId>,
|
||||
backend: Option<Backend>,
|
||||
}
|
||||
|
||||
impl ConsoleManager {
|
||||
/// Create a console manager instance.
|
||||
pub fn new(epoll_mgr: EpollManager, logger: &slog::Logger) -> Self {
|
||||
let logger = logger.new(slog::o!("subsystem" => "console_manager"));
|
||||
ConsoleManager {
|
||||
epoll_mgr,
|
||||
logger,
|
||||
subscriber_id: Default::default(),
|
||||
backend: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a console backend device by using stdio streams.
|
||||
pub fn create_stdio_console(&mut self, device: Arc<Mutex<SerialDevice>>) -> Result<()> {
|
||||
let stdin_handle = std::io::stdin();
|
||||
stdin_handle
|
||||
.lock()
|
||||
.set_raw_mode()
|
||||
.map_err(|e| DeviceMgrError::ConsoleManager(ConsoleManagerError::StdinHandle(e)))?;
|
||||
|
||||
let handler = ConsoleEpollHandler::new(device, Some(stdin_handle), None, &self.logger);
|
||||
self.subscriber_id = Some(self.epoll_mgr.add_subscriber(Box::new(handler)));
|
||||
self.backend = Some(Backend::StdinHandle(std::io::stdin()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create s console backend device by using Unix Domain socket.
|
||||
pub fn create_socket_console(
|
||||
&mut self,
|
||||
device: Arc<Mutex<SerialDevice>>,
|
||||
sock_path: String,
|
||||
) -> Result<()> {
|
||||
let sock_listener = Self::bind_domain_socket(&sock_path).map_err(|e| {
|
||||
DeviceMgrError::ConsoleManager(ConsoleManagerError::CreateSerialSock(e))
|
||||
})?;
|
||||
let handler = ConsoleEpollHandler::new(device, None, Some(sock_listener), &self.logger);
|
||||
|
||||
self.subscriber_id = Some(self.epoll_mgr.add_subscriber(Box::new(handler)));
|
||||
self.backend = Some(Backend::SockPath(sock_path));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the host side terminal to canonical mode.
|
||||
pub fn reset_console(&self) -> Result<()> {
|
||||
if let Some(Backend::StdinHandle(stdin_handle)) = self.backend.as_ref() {
|
||||
stdin_handle
|
||||
.lock()
|
||||
.set_canon_mode()
|
||||
.map_err(|e| DeviceMgrError::ConsoleManager(ConsoleManagerError::StdinHandle(e)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bind_domain_socket(serial_path: &str) -> std::result::Result<UnixListener, std::io::Error> {
|
||||
let path = Path::new(serial_path);
|
||||
if path.is_file() {
|
||||
let _ = std::fs::remove_file(serial_path);
|
||||
}
|
||||
|
||||
UnixListener::bind(path)
|
||||
}
|
||||
}
|
||||
|
||||
struct ConsoleEpollHandler {
|
||||
device: Arc<Mutex<SerialDevice>>,
|
||||
stdin_handle: Option<std::io::Stdin>,
|
||||
sock_listener: Option<UnixListener>,
|
||||
sock_conn: Option<UnixStream>,
|
||||
logger: slog::Logger,
|
||||
}
|
||||
|
||||
impl ConsoleEpollHandler {
|
||||
fn new(
|
||||
device: Arc<Mutex<SerialDevice>>,
|
||||
stdin_handle: Option<std::io::Stdin>,
|
||||
sock_listener: Option<UnixListener>,
|
||||
logger: &slog::Logger,
|
||||
) -> Self {
|
||||
ConsoleEpollHandler {
|
||||
device,
|
||||
stdin_handle,
|
||||
sock_listener,
|
||||
sock_conn: None,
|
||||
logger: logger.new(slog::o!("subsystem" => "console_manager")),
|
||||
}
|
||||
}
|
||||
|
||||
fn uds_listener_accept(&mut self, ops: &mut EventOps) -> std::io::Result<()> {
|
||||
if self.sock_conn.is_some() {
|
||||
slog::warn!(self.logger,
|
||||
"UDS for serial port 1 already exists, reject the new connection";
|
||||
"subsystem" => "console_mgr",
|
||||
);
|
||||
// Do not expected poisoned lock.
|
||||
let _ = self.sock_listener.as_mut().unwrap().accept();
|
||||
} else {
|
||||
// Safe to unwrap() because self.sock_conn is Some().
|
||||
let (conn_sock, _) = self.sock_listener.as_ref().unwrap().accept()?;
|
||||
let events = Events::with_data(&conn_sock, EPOLL_EVENT_SERIAL_DATA, EventSet::IN);
|
||||
if let Err(e) = ops.add(events) {
|
||||
slog::error!(self.logger,
|
||||
"failed to register epoll event for serial, {:?}", e;
|
||||
"subsystem" => "console_mgr",
|
||||
);
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let conn_sock_copy = conn_sock.try_clone()?;
|
||||
// Do not expected poisoned lock.
|
||||
self.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_output_stream(Some(Box::new(conn_sock_copy)));
|
||||
|
||||
self.sock_conn = Some(conn_sock);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uds_read_in(&mut self, ops: &mut EventOps) -> std::io::Result<()> {
|
||||
let mut should_drop = true;
|
||||
|
||||
if let Some(conn_sock) = self.sock_conn.as_mut() {
|
||||
let mut out = [0u8; MAX_BACKEND_THROUGHPUT];
|
||||
match conn_sock.read(&mut out[..]) {
|
||||
Ok(0) => {
|
||||
// Zero-length read means EOF. Remove this conn sock.
|
||||
self.device
|
||||
.lock()
|
||||
.expect("console: poisoned console lock")
|
||||
.set_output_stream(None);
|
||||
}
|
||||
Ok(count) => {
|
||||
self.device
|
||||
.lock()
|
||||
.expect("console: poisoned console lock")
|
||||
.raw_input(&out[..count])?;
|
||||
should_drop = false;
|
||||
}
|
||||
Err(e) => {
|
||||
slog::warn!(self.logger,
|
||||
"error while reading serial conn sock: {:?}", e;
|
||||
"subsystem" => "console_mgr"
|
||||
);
|
||||
self.device
|
||||
.lock()
|
||||
.expect("console: poisoned console lock")
|
||||
.set_output_stream(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if should_drop {
|
||||
assert!(self.sock_conn.is_some());
|
||||
// Safe to unwrap() because self.sock_conn is Some().
|
||||
let sock_conn = self.sock_conn.take().unwrap();
|
||||
let events = Events::with_data(&sock_conn, EPOLL_EVENT_SERIAL_DATA, EventSet::IN);
|
||||
if let Err(e) = ops.remove(events) {
|
||||
slog::error!(self.logger,
|
||||
"failed deregister epoll event for UDS, {:?}", e;
|
||||
"subsystem" => "console_mgr"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stdio_read_in(&mut self, ops: &mut EventOps) -> std::io::Result<()> {
|
||||
let mut should_drop = true;
|
||||
|
||||
if let Some(handle) = self.stdin_handle.as_ref() {
|
||||
let mut out = [0u8; MAX_BACKEND_THROUGHPUT];
|
||||
// Safe to unwrap() because self.stdin_handle is Some().
|
||||
let stdin_lock = handle.lock();
|
||||
match stdin_lock.read_raw(&mut out[..]) {
|
||||
Ok(0) => {
|
||||
// Zero-length read indicates EOF. Remove from pollables.
|
||||
self.device
|
||||
.lock()
|
||||
.expect("console: poisoned console lock")
|
||||
.set_output_stream(None);
|
||||
}
|
||||
Ok(count) => {
|
||||
self.device
|
||||
.lock()
|
||||
.expect("console: poisoned console lock")
|
||||
.raw_input(&out[..count])?;
|
||||
should_drop = false;
|
||||
}
|
||||
Err(e) => {
|
||||
slog::warn!(self.logger,
|
||||
"error while reading stdin: {:?}", e;
|
||||
"subsystem" => "console_mgr"
|
||||
);
|
||||
self.device
|
||||
.lock()
|
||||
.expect("console: poisoned console lock")
|
||||
.set_output_stream(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if should_drop {
|
||||
let events = Events::with_data_raw(libc::STDIN_FILENO, EPOLL_EVENT_STDIN, EventSet::IN);
|
||||
if let Err(e) = ops.remove(events) {
|
||||
slog::error!(self.logger,
|
||||
"failed to deregister epoll event for stdin, {:?}", e;
|
||||
"subsystem" => "console_mgr"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MutEventSubscriber for ConsoleEpollHandler {
|
||||
fn process(&mut self, events: Events, ops: &mut EventOps) {
|
||||
slog::trace!(self.logger, "ConsoleEpollHandler::process()");
|
||||
let slot = events.data();
|
||||
match slot {
|
||||
EPOLL_EVENT_SERIAL => {
|
||||
if let Err(e) = self.uds_listener_accept(ops) {
|
||||
slog::warn!(self.logger, "failed to accept incoming connection, {:?}", e);
|
||||
}
|
||||
}
|
||||
EPOLL_EVENT_SERIAL_DATA => {
|
||||
if let Err(e) = self.uds_read_in(ops) {
|
||||
slog::warn!(self.logger, "failed to read data from UDS, {:?}", e);
|
||||
}
|
||||
}
|
||||
EPOLL_EVENT_STDIN => {
|
||||
if let Err(e) = self.stdio_read_in(ops) {
|
||||
slog::warn!(self.logger, "failed to read data from stdin, {:?}", e);
|
||||
}
|
||||
}
|
||||
_ => slog::error!(self.logger, "unknown epoll slot number {}", slot),
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self, ops: &mut EventOps) {
|
||||
slog::trace!(self.logger, "ConsoleEpollHandler::init()");
|
||||
|
||||
if self.stdin_handle.is_some() {
|
||||
slog::info!(self.logger, "ConsoleEpollHandler: stdin handler");
|
||||
let events = Events::with_data_raw(libc::STDIN_FILENO, EPOLL_EVENT_STDIN, EventSet::IN);
|
||||
if let Err(e) = ops.add(events) {
|
||||
slog::error!(
|
||||
self.logger,
|
||||
"failed to register epoll event for stdin, {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(sock) = self.sock_listener.as_ref() {
|
||||
slog::info!(self.logger, "ConsoleEpollHandler: sock listener");
|
||||
let events = Events::with_data(sock, EPOLL_EVENT_SERIAL, EventSet::IN);
|
||||
if let Err(e) = ops.add(events) {
|
||||
slog::error!(
|
||||
self.logger,
|
||||
"failed to register epoll event for UDS listener, {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(conn) = self.sock_conn.as_ref() {
|
||||
slog::info!(self.logger, "ConsoleEpollHandler: sock connection");
|
||||
let events = Events::with_data(conn, EPOLL_EVENT_SERIAL_DATA, EventSet::IN);
|
||||
if let Err(e) = ops.add(events) {
|
||||
slog::error!(
|
||||
self.logger,
|
||||
"failed to register epoll event for UDS connection, {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writer to process guest kernel dmesg.
|
||||
pub struct DmesgWriter {
|
||||
buf: BytesMut,
|
||||
logger: slog::Logger,
|
||||
}
|
||||
|
||||
impl io::Write for DmesgWriter {
|
||||
/// 0000000 [ 0 . 0 3 4 9 1 6 ] R
|
||||
/// 5b 20 20 20 20 30 2e 30 33 34 39 31 36 5d 20 52
|
||||
/// 0000020 u n / s b i n / i n i t a s
|
||||
/// 75 6e 20 2f 73 62 69 6e 2f 69 6e 69 74 20 61 73
|
||||
/// 0000040 i n i t p r o c e s s \r \n [
|
||||
///
|
||||
/// dmesg message end a line with /r/n . When redirect message to logger, we should
|
||||
/// remove the /r/n .
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let arr: Vec<&[u8]> = buf.split(|c| *c == b'\n').collect();
|
||||
let count = arr.len();
|
||||
|
||||
for (i, sub) in arr.iter().enumerate() {
|
||||
if sub.is_empty() {
|
||||
if !self.buf.is_empty() {
|
||||
slog::info!(
|
||||
self.logger,
|
||||
"{}",
|
||||
String::from_utf8_lossy(self.buf.as_ref()).trim_end()
|
||||
);
|
||||
self.buf.clear();
|
||||
}
|
||||
} else if sub.len() < buf.len() && i < count - 1 {
|
||||
slog::info!(
|
||||
self.logger,
|
||||
"{}{}",
|
||||
String::from_utf8_lossy(self.buf.as_ref()).trim_end(),
|
||||
String::from_utf8_lossy(sub).trim_end(),
|
||||
);
|
||||
self.buf.clear();
|
||||
} else {
|
||||
self.buf.put_slice(sub);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use slog::Drain;
|
||||
use std::io::Write;
|
||||
|
||||
fn create_logger() -> slog::Logger {
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
let drain = slog_term::FullFormat::new(decorator).build().fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
slog::Logger::root(drain, slog::o!())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dmesg_writer() {
|
||||
let mut writer = DmesgWriter {
|
||||
buf: Default::default(),
|
||||
logger: create_logger(),
|
||||
};
|
||||
|
||||
writer.flush().unwrap();
|
||||
writer.write("".as_bytes()).unwrap();
|
||||
writer.write("\n".as_bytes()).unwrap();
|
||||
writer.write("\n\n".as_bytes()).unwrap();
|
||||
writer.write("\n\n\n".as_bytes()).unwrap();
|
||||
writer.write("12\n23\n34\n56".as_bytes()).unwrap();
|
||||
writer.write("78".as_bytes()).unwrap();
|
||||
writer.write("90\n".as_bytes()).unwrap();
|
||||
writer.flush().unwrap();
|
||||
}
|
||||
|
||||
// TODO: add unit tests for console manager
|
||||
}
|
20
src/dragonball/src/device_manager/mod.rs
Normal file
20
src/dragonball/src/device_manager/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Device manager to manage IO devices for a virtual machine.
|
||||
|
||||
/// Virtual machine console device manager.
|
||||
pub mod console_manager;
|
||||
/// Console Manager for virtual machines console device.
|
||||
pub use self::console_manager::ConsoleManager;
|
||||
|
||||
/// Errors related to device manager operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DeviceMgrError {
|
||||
/// Failed to manage console devices.
|
||||
#[error(transparent)]
|
||||
ConsoleManager(console_manager::ConsoleManagerError),
|
||||
}
|
||||
|
||||
/// Specialized version of `std::result::Result` for device manager operations.
|
||||
pub type Result<T> = ::std::result::Result<T, DeviceMgrError>;
|
@ -4,8 +4,12 @@
|
||||
//! Dragonball is a light-weight virtual machine manager(VMM) based on Linux Kernel-based Virtual
|
||||
//! Machine(KVM) which is optimized for container workloads.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
/// Address space manager for virtual machines.
|
||||
pub mod address_space_manager;
|
||||
/// Device manager for virtual machines.
|
||||
pub mod device_manager;
|
||||
/// Resource manager for virtual machines.
|
||||
pub mod resource_manager;
|
||||
/// Virtual machine manager for virtual machines.
|
||||
|
Loading…
Reference in New Issue
Block a user