mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-28 08:17:37 +00:00
dragonball: add Vmm struct
The Vmm struct is global coordinator to manage API servers, virtual machines etc. Signed-off-by: wllenyj <wllenyj@linux.alibaba.com>
This commit is contained in:
parent
4d234f5742
commit
5c1ccc376b
59
src/dragonball/src/api/v1/boot_source.rs
Normal file
59
src/dragonball/src/api/v1/boot_source.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Default guest kernel command line:
|
||||||
|
/// - `reboot=k` shut down the guest on reboot, instead of well... rebooting;
|
||||||
|
/// - `panic=1` on panic, reboot after 1 second;
|
||||||
|
/// - `pci=off` do not scan for PCI devices (ser boot time);
|
||||||
|
/// - `nomodules` disable loadable kernel module support;
|
||||||
|
/// - `8250.nr_uarts=0` disable 8250 serial interface;
|
||||||
|
/// - `i8042.noaux` do not probe the i8042 controller for an attached mouse (ser boot time);
|
||||||
|
/// - `i8042.nomux` do not probe i8042 for a multiplexing controller (ser boot time);
|
||||||
|
/// - `i8042.nopnp` do not use ACPIPnP to discover KBD/AUX controllers (ser boot time);
|
||||||
|
/// - `i8042.dumbkbd` do not attempt to control kbd state via the i8042 (ser boot time).
|
||||||
|
pub const DEFAULT_KERNEL_CMDLINE: &str = "reboot=k panic=1 pci=off nomodules 8250.nr_uarts=0 \
|
||||||
|
i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd";
|
||||||
|
|
||||||
|
/// Strongly typed data structure used to configure the boot source of the microvm.
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct BootSourceConfig {
|
||||||
|
/// Path of the kernel image.
|
||||||
|
/// We only support uncompressed kernel for Dragonball.
|
||||||
|
pub kernel_path: String,
|
||||||
|
/// Path of the initrd, if there is one.
|
||||||
|
/// ps. rootfs is set in BlockDeviceConfigInfo
|
||||||
|
pub initrd_path: Option<String>,
|
||||||
|
/// The boot arguments to pass to the kernel.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub boot_args: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors associated with actions on `BootSourceConfig`.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum BootSourceConfigError {
|
||||||
|
/// The virutal machine instance ID is invalid.
|
||||||
|
#[error("the virtual machine instance ID is invalid")]
|
||||||
|
InvalidVMID,
|
||||||
|
|
||||||
|
/// The kernel file cannot be opened.
|
||||||
|
#[error(
|
||||||
|
"the kernel file cannot be opened due to invalid kernel path or invalid permissions: {0}"
|
||||||
|
)]
|
||||||
|
InvalidKernelPath(#[source] std::io::Error),
|
||||||
|
|
||||||
|
/// The initrd file cannot be opened.
|
||||||
|
#[error("the initrd file cannot be opened due to invalid path or invalid permissions: {0}")]
|
||||||
|
InvalidInitrdPath(#[source] std::io::Error),
|
||||||
|
|
||||||
|
/// The kernel command line is invalid.
|
||||||
|
#[error("the kernel command line is invalid: {0}")]
|
||||||
|
InvalidKernelCommandLine(#[source] linux_loader::cmdline::Error),
|
||||||
|
|
||||||
|
/// The boot source cannot be update post boot.
|
||||||
|
#[error("the update operation is not allowed after boot")]
|
||||||
|
UpdateNotAllowedPostBoot,
|
||||||
|
}
|
@ -3,5 +3,15 @@
|
|||||||
|
|
||||||
//! API Version 1 related data structures to configure the vmm.
|
//! API Version 1 related data structures to configure the vmm.
|
||||||
|
|
||||||
|
mod vmm_action;
|
||||||
|
pub use self::vmm_action::{
|
||||||
|
VmmAction, VmmActionError, VmmData, VmmRequest, VmmResponse, VmmService,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Wrapper for configuring the microVM boot source.
|
||||||
|
mod boot_source;
|
||||||
|
pub use self::boot_source::{BootSourceConfig, BootSourceConfigError, DEFAULT_KERNEL_CMDLINE};
|
||||||
|
|
||||||
|
/// Wrapper over the microVM general information.
|
||||||
mod instance_info;
|
mod instance_info;
|
||||||
pub use self::instance_info::{InstanceInfo, InstanceState};
|
pub use self::instance_info::{InstanceInfo, InstanceState};
|
||||||
|
148
src/dragonball/src/api/v1/vmm_action.rs
Normal file
148
src/dragonball/src/api/v1/vmm_action.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright (C) 2020-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.
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
|
||||||
|
|
||||||
|
use log::{debug, error, warn};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::event_manager::EventManager;
|
||||||
|
use crate::vm::{KernelConfigInfo, VmConfigInfo};
|
||||||
|
use crate::vmm::Vmm;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Wrapper for all errors associated with VMM actions.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum VmmActionError {
|
||||||
|
/// The action `ConfigureBootSource` failed either because of bad user input or an internal
|
||||||
|
/// error.
|
||||||
|
#[error("failed to configure boot source for VM: {0}")]
|
||||||
|
BootSource(#[source] BootSourceConfigError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This enum represents the public interface of the VMM. Each action contains various
|
||||||
|
/// bits of information (ids, paths, etc.).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum VmmAction {
|
||||||
|
/// Configure the boot source of the microVM using as input the `ConfigureBootSource`. This
|
||||||
|
/// action can only be called before the microVM has booted.
|
||||||
|
ConfigureBootSource(BootSourceConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The enum represents the response sent by the VMM in case of success. The response is either
|
||||||
|
/// empty, when no data needs to be sent, or an internal VMM structure.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VmmData {
|
||||||
|
/// No data is sent on the channel.
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request data type used to communicate between the API and the VMM.
|
||||||
|
pub type VmmRequest = Box<VmmAction>;
|
||||||
|
|
||||||
|
/// Data type used to communicate between the API and the VMM.
|
||||||
|
pub type VmmRequestResult = std::result::Result<VmmData, VmmActionError>;
|
||||||
|
|
||||||
|
/// Response data type used to communicate between the API and the VMM.
|
||||||
|
pub type VmmResponse = Box<VmmRequestResult>;
|
||||||
|
|
||||||
|
/// VMM Service to handle requests from the API server.
|
||||||
|
///
|
||||||
|
/// There are two levels of API servers as below:
|
||||||
|
/// API client <--> VMM API Server <--> VMM Core
|
||||||
|
pub struct VmmService {
|
||||||
|
from_api: Receiver<VmmRequest>,
|
||||||
|
to_api: Sender<VmmResponse>,
|
||||||
|
machine_config: VmConfigInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VmmService {
|
||||||
|
/// Create a new VMM API server instance.
|
||||||
|
pub fn new(from_api: Receiver<VmmRequest>, to_api: Sender<VmmResponse>) -> Self {
|
||||||
|
VmmService {
|
||||||
|
from_api,
|
||||||
|
to_api,
|
||||||
|
machine_config: VmConfigInfo::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle requests from the HTTP API Server and send back replies.
|
||||||
|
pub fn run_vmm_action(&mut self, vmm: &mut Vmm, _event_mgr: &mut EventManager) -> Result<()> {
|
||||||
|
let request = match self.from_api.try_recv() {
|
||||||
|
Ok(t) => *t,
|
||||||
|
Err(TryRecvError::Empty) => {
|
||||||
|
warn!("Got a spurious notification from api thread");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => {
|
||||||
|
panic!("The channel's sending half was disconnected. Cannot receive data.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!("receive vmm action: {:?}", request);
|
||||||
|
|
||||||
|
let response = match request {
|
||||||
|
VmmAction::ConfigureBootSource(boot_source_body) => {
|
||||||
|
self.configure_boot_source(vmm, boot_source_body)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("send vmm response: {:?}", response);
|
||||||
|
self.send_response(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_response(&self, result: VmmRequestResult) -> Result<()> {
|
||||||
|
self.to_api
|
||||||
|
.send(Box::new(result))
|
||||||
|
.map_err(|_| ())
|
||||||
|
.expect("vmm: one-shot API result channel has been closed");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_boot_source(
|
||||||
|
&self,
|
||||||
|
vmm: &mut Vmm,
|
||||||
|
boot_source_config: BootSourceConfig,
|
||||||
|
) -> VmmRequestResult {
|
||||||
|
use super::BootSourceConfigError::{
|
||||||
|
InvalidInitrdPath, InvalidKernelCommandLine, InvalidKernelPath, InvalidVMID,
|
||||||
|
UpdateNotAllowedPostBoot,
|
||||||
|
};
|
||||||
|
use super::VmmActionError::BootSource;
|
||||||
|
|
||||||
|
let vm = vmm.get_vm_by_id_mut("").ok_or(BootSource(InvalidVMID))?;
|
||||||
|
if vm.is_vm_initialized() {
|
||||||
|
return Err(BootSource(UpdateNotAllowedPostBoot));
|
||||||
|
}
|
||||||
|
|
||||||
|
let kernel_file = File::open(&boot_source_config.kernel_path)
|
||||||
|
.map_err(|e| BootSource(InvalidKernelPath(e)))?;
|
||||||
|
|
||||||
|
let initrd_file = match boot_source_config.initrd_path {
|
||||||
|
None => None,
|
||||||
|
Some(ref path) => Some(File::open(path).map_err(|e| BootSource(InvalidInitrdPath(e)))?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cmdline = linux_loader::cmdline::Cmdline::new(dbs_boot::layout::CMDLINE_MAX_SIZE);
|
||||||
|
let boot_args = boot_source_config
|
||||||
|
.boot_args
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| String::from(DEFAULT_KERNEL_CMDLINE));
|
||||||
|
cmdline
|
||||||
|
.insert_str(boot_args)
|
||||||
|
.map_err(|e| BootSource(InvalidKernelCommandLine(e)))?;
|
||||||
|
|
||||||
|
let kernel_config = KernelConfigInfo::new(kernel_file, initrd_file, cmdline);
|
||||||
|
vm.set_kernel_config(kernel_config);
|
||||||
|
|
||||||
|
Ok(VmmData::Empty)
|
||||||
|
}
|
||||||
|
}
|
@ -178,3 +178,24 @@ pub enum LoadInitrdError {
|
|||||||
#[error("failed to read the initrd image: {0}")]
|
#[error("failed to read the initrd image: {0}")]
|
||||||
ReadInitrd(#[source] std::io::Error),
|
ReadInitrd(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A dedicated error type to glue with the vmm_epoll crate.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum EpollError {
|
||||||
|
/// Generic internal error.
|
||||||
|
#[error("unclassfied internal error")]
|
||||||
|
InternalError,
|
||||||
|
|
||||||
|
/// Errors from the epoll subsystem.
|
||||||
|
#[error("failed to issue epoll syscall: {0}")]
|
||||||
|
EpollMgr(#[from] dbs_utils::epoll_manager::Error),
|
||||||
|
|
||||||
|
/// Generic IO errors.
|
||||||
|
#[error(transparent)]
|
||||||
|
IOError(std::io::Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "dbs-virtio-devices")]
|
||||||
|
/// Errors from virtio devices.
|
||||||
|
#[error("failed to manager Virtio device: {0}")]
|
||||||
|
VirtIoDevice(#[source] VirtIoError),
|
||||||
|
}
|
||||||
|
169
src/dragonball/src/event_manager.rs
Normal file
169
src/dragonball/src/event_manager.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright (C) 2020-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.
|
||||||
|
|
||||||
|
//! Event manager to manage and handle IO events and requests from API server .
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use dbs_utils::epoll_manager::{
|
||||||
|
EpollManager, EventOps, EventSet, Events, MutEventSubscriber, SubscriberId,
|
||||||
|
};
|
||||||
|
use log::{error, warn};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use crate::error::{EpollError, Result};
|
||||||
|
use crate::vmm::Vmm;
|
||||||
|
|
||||||
|
// Statically assigned epoll slot for VMM events.
|
||||||
|
pub(crate) const EPOLL_EVENT_EXIT: u32 = 0;
|
||||||
|
pub(crate) const EPOLL_EVENT_API_REQUEST: u32 = 1;
|
||||||
|
|
||||||
|
/// Shared information between vmm::vmm_thread_event_loop() and VmmEpollHandler.
|
||||||
|
pub(crate) struct EventContext {
|
||||||
|
pub api_event_fd: EventFd,
|
||||||
|
pub api_event_flag: bool,
|
||||||
|
pub exit_evt_flag: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventContext {
|
||||||
|
/// Create a new instance of [`EventContext`].
|
||||||
|
pub fn new(api_event_fd: EventFd) -> Result<Self> {
|
||||||
|
Ok(EventContext {
|
||||||
|
api_event_fd,
|
||||||
|
api_event_flag: false,
|
||||||
|
exit_evt_flag: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event manager for VMM to handle API requests and IO events.
|
||||||
|
pub struct EventManager {
|
||||||
|
epoll_mgr: EpollManager,
|
||||||
|
subscriber_id: SubscriberId,
|
||||||
|
vmm_event_count: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EventManager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Vmm -> Vm -> EpollManager -> VmmEpollHandler -> Vmm
|
||||||
|
// We need to remove VmmEpollHandler to break the circular reference
|
||||||
|
// so that Vmm can drop.
|
||||||
|
self.epoll_mgr
|
||||||
|
.remove_subscriber(self.subscriber_id)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("event_manager: remove_subscriber err. {:?}", e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventManager {
|
||||||
|
/// Create a new event manager associated with the VMM object.
|
||||||
|
pub fn new(vmm: &Arc<Mutex<Vmm>>, epoll_mgr: EpollManager) -> Result<Self> {
|
||||||
|
let vmm_event_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
let handler: Box<dyn MutEventSubscriber + Send> = Box::new(VmmEpollHandler {
|
||||||
|
vmm: vmm.clone(),
|
||||||
|
vmm_event_count: vmm_event_count.clone(),
|
||||||
|
});
|
||||||
|
let subscriber_id = epoll_mgr.add_subscriber(handler);
|
||||||
|
|
||||||
|
Ok(EventManager {
|
||||||
|
epoll_mgr,
|
||||||
|
subscriber_id,
|
||||||
|
vmm_event_count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying epoll event manager.
|
||||||
|
pub fn epoll_manager(&self) -> EpollManager {
|
||||||
|
self.epoll_mgr.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registry the eventfd for exit notification.
|
||||||
|
pub fn register_exit_eventfd(
|
||||||
|
&mut self,
|
||||||
|
exit_evt: &EventFd,
|
||||||
|
) -> std::result::Result<(), EpollError> {
|
||||||
|
let events = Events::with_data(exit_evt, EPOLL_EVENT_EXIT, EventSet::IN);
|
||||||
|
|
||||||
|
self.epoll_mgr
|
||||||
|
.add_event(self.subscriber_id, events)
|
||||||
|
.map_err(EpollError::EpollMgr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll pending events and invoke registered event handler.
|
||||||
|
///
|
||||||
|
/// # Arguments:
|
||||||
|
/// * max_events: maximum number of pending events to handle
|
||||||
|
/// * timeout: maximum time in milliseconds to wait
|
||||||
|
pub fn handle_events(&self, timeout: i32) -> std::result::Result<usize, EpollError> {
|
||||||
|
self.epoll_mgr
|
||||||
|
.handle_events(timeout)
|
||||||
|
.map_err(EpollError::EpollMgr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the VMM event count and reset it to zero.
|
||||||
|
pub fn fetch_vmm_event_count(&self) -> usize {
|
||||||
|
self.vmm_event_count.swap(0, Ordering::AcqRel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VmmEpollHandler {
|
||||||
|
vmm: Arc<Mutex<Vmm>>,
|
||||||
|
vmm_event_count: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MutEventSubscriber for VmmEpollHandler {
|
||||||
|
fn process(&mut self, events: Events, _ops: &mut EventOps) {
|
||||||
|
// Do not try to recover when the lock has already been poisoned.
|
||||||
|
// And be careful to avoid deadlock between process() and vmm::vmm_thread_event_loop().
|
||||||
|
let mut vmm = self.vmm.lock().unwrap();
|
||||||
|
|
||||||
|
match events.data() {
|
||||||
|
EPOLL_EVENT_API_REQUEST => {
|
||||||
|
if let Err(e) = vmm.event_ctx.api_event_fd.read() {
|
||||||
|
error!("event_manager: failed to read API eventfd, {:?}", e);
|
||||||
|
}
|
||||||
|
vmm.event_ctx.api_event_flag = true;
|
||||||
|
self.vmm_event_count.fetch_add(1, Ordering::AcqRel);
|
||||||
|
}
|
||||||
|
EPOLL_EVENT_EXIT => {
|
||||||
|
let vm = vmm.get_vm_by_id("").unwrap();
|
||||||
|
match vm.get_reset_eventfd() {
|
||||||
|
Some(ev) => {
|
||||||
|
if let Err(e) = ev.read() {
|
||||||
|
error!("event_manager: failed to read exit eventfd, {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => warn!("event_manager: leftover exit event in epoll context!"),
|
||||||
|
}
|
||||||
|
vmm.event_ctx.exit_evt_flag = true;
|
||||||
|
self.vmm_event_count.fetch_add(1, Ordering::AcqRel);
|
||||||
|
}
|
||||||
|
_ => error!("event_manager: unknown epoll slot number {}", events.data()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self, ops: &mut EventOps) {
|
||||||
|
// Do not expect poisoned lock.
|
||||||
|
let vmm = self.vmm.lock().unwrap();
|
||||||
|
let events = Events::with_data(
|
||||||
|
&vmm.event_ctx.api_event_fd,
|
||||||
|
EPOLL_EVENT_API_REQUEST,
|
||||||
|
EventSet::IN,
|
||||||
|
);
|
||||||
|
if let Err(e) = ops.add(events) {
|
||||||
|
error!(
|
||||||
|
"event_manager: failed to register epoll event for API server, {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,8 +32,13 @@ pub mod vcpu;
|
|||||||
/// Virtual machine manager for virtual machines.
|
/// Virtual machine manager for virtual machines.
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
|
|
||||||
|
mod event_manager;
|
||||||
mod io_manager;
|
mod io_manager;
|
||||||
|
mod vmm;
|
||||||
|
|
||||||
|
pub use self::error::StartMicrovmError;
|
||||||
pub use self::io_manager::IoManagerCached;
|
pub use self::io_manager::IoManagerCached;
|
||||||
|
pub use self::vmm::Vmm;
|
||||||
|
|
||||||
/// Success exit code.
|
/// Success exit code.
|
||||||
pub const EXIT_CODE_OK: u8 = 0;
|
pub const EXIT_CODE_OK: u8 = 0;
|
||||||
|
215
src/dragonball/src/vmm.rs
Normal file
215
src/dragonball/src/vmm.rs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
// Copyright (C) 2020-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.
|
||||||
|
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
|
use dbs_utils::epoll_manager::EpollManager;
|
||||||
|
use log::{error, info, warn};
|
||||||
|
use seccompiler::BpfProgram;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use crate::api::v1::{InstanceInfo, VmmService};
|
||||||
|
use crate::error::{EpollError, Result};
|
||||||
|
use crate::event_manager::{EventContext, EventManager};
|
||||||
|
use crate::vm::Vm;
|
||||||
|
use crate::{EXIT_CODE_GENERIC_ERROR, EXIT_CODE_OK};
|
||||||
|
|
||||||
|
/// Global coordinator to manage API servers, virtual machines, upgrade etc.
|
||||||
|
///
|
||||||
|
/// Originally firecracker assumes an VMM only manages an VM, and doesn't distinguish VMM and VM.
|
||||||
|
/// Thus caused a mixed and confusion design. Now we have explicit build the object model as:
|
||||||
|
/// |---Vmm API Server--<-1:1-> HTTP API Server
|
||||||
|
/// | |----------<-1:1-> Shimv2/CRI API Server
|
||||||
|
/// |
|
||||||
|
/// Vmm <-1:N-> Vm <-1:1-> Address Space Manager <-1:N-> GuestMemory
|
||||||
|
/// ^ ^---1:1-> Device Manager <-1:N-> Device
|
||||||
|
/// | ^---1:1-> Resource Manager
|
||||||
|
/// | ^---1:N-> Vcpu
|
||||||
|
/// |---<-1:N-> Event Manager
|
||||||
|
pub struct Vmm {
|
||||||
|
pub(crate) event_ctx: EventContext,
|
||||||
|
epoll_manager: EpollManager,
|
||||||
|
|
||||||
|
// Will change to a HashMap when enabling 1 VMM with multiple VMs.
|
||||||
|
vm: Vm,
|
||||||
|
|
||||||
|
vcpu_seccomp_filter: BpfProgram,
|
||||||
|
vmm_seccomp_filter: BpfProgram,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vmm {
|
||||||
|
/// Create a Virtual Machine Monitor instance.
|
||||||
|
pub fn new(
|
||||||
|
api_shared_info: Arc<RwLock<InstanceInfo>>,
|
||||||
|
api_event_fd: EventFd,
|
||||||
|
vmm_seccomp_filter: BpfProgram,
|
||||||
|
vcpu_seccomp_filter: BpfProgram,
|
||||||
|
kvm_fd: Option<RawFd>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let epoll_manager = EpollManager::default();
|
||||||
|
Self::new_with_epoll_manager(
|
||||||
|
api_shared_info,
|
||||||
|
api_event_fd,
|
||||||
|
epoll_manager,
|
||||||
|
vmm_seccomp_filter,
|
||||||
|
vcpu_seccomp_filter,
|
||||||
|
kvm_fd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a Virtual Machine Monitor instance with a epoll_manager.
|
||||||
|
pub fn new_with_epoll_manager(
|
||||||
|
api_shared_info: Arc<RwLock<InstanceInfo>>,
|
||||||
|
api_event_fd: EventFd,
|
||||||
|
epoll_manager: EpollManager,
|
||||||
|
vmm_seccomp_filter: BpfProgram,
|
||||||
|
vcpu_seccomp_filter: BpfProgram,
|
||||||
|
kvm_fd: Option<RawFd>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let vm = Vm::new(kvm_fd, api_shared_info, epoll_manager.clone())?;
|
||||||
|
let event_ctx = EventContext::new(api_event_fd)?;
|
||||||
|
|
||||||
|
Ok(Vmm {
|
||||||
|
event_ctx,
|
||||||
|
epoll_manager,
|
||||||
|
vm,
|
||||||
|
vcpu_seccomp_filter,
|
||||||
|
vmm_seccomp_filter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to a virtual machine managed by the VMM.
|
||||||
|
pub fn get_vm_by_id(&self, _id: &str) -> Option<&Vm> {
|
||||||
|
Some(&self.vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to a virtual machine managed by the VMM.
|
||||||
|
pub fn get_vm_by_id_mut(&mut self, _id: &str) -> Option<&mut Vm> {
|
||||||
|
Some(&mut self.vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the seccomp rules for vCPU threads.
|
||||||
|
pub fn vcpu_seccomp_filter(&self) -> BpfProgram {
|
||||||
|
self.vcpu_seccomp_filter.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the seccomp rules for VMM threads.
|
||||||
|
pub fn vmm_seccomp_filter(&self) -> BpfProgram {
|
||||||
|
self.vmm_seccomp_filter.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the event loop to service API requests.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `vmm` - An Arc reference to the global Vmm instance.
|
||||||
|
/// * `service` - VMM Service provider.
|
||||||
|
pub fn run_vmm_event_loop(vmm: Arc<Mutex<Vmm>>, mut service: VmmService) -> i32 {
|
||||||
|
let epoll_mgr = vmm.lock().unwrap().epoll_manager.clone();
|
||||||
|
let mut event_mgr =
|
||||||
|
EventManager::new(&vmm, epoll_mgr).expect("Cannot create epoll manager");
|
||||||
|
|
||||||
|
'poll: loop {
|
||||||
|
match event_mgr.handle_events(-1) {
|
||||||
|
Ok(_) => {
|
||||||
|
// Check whether there are pending vmm events.
|
||||||
|
if event_mgr.fetch_vmm_event_count() == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut v = vmm.lock().unwrap();
|
||||||
|
if v.event_ctx.api_event_flag {
|
||||||
|
// The run_vmm_action() needs to access event_mgr, so it could
|
||||||
|
// not be handled in EpollHandler::handle_events(). It has been
|
||||||
|
// delayed to the main loop.
|
||||||
|
v.event_ctx.api_event_flag = false;
|
||||||
|
service
|
||||||
|
.run_vmm_action(&mut v, &mut event_mgr)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
warn!("got spurious notification from api thread");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if v.event_ctx.exit_evt_flag {
|
||||||
|
info!("Gracefully terminated VMM control loop");
|
||||||
|
return v.stop(EXIT_CODE_OK as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Abruptly exited VMM control loop: {:?}", e);
|
||||||
|
if let EpollError::EpollMgr(dbs_utils::epoll_manager::Error::Epoll(e)) = e {
|
||||||
|
if e.errno() == libc::EAGAIN || e.errno() == libc::EINTR {
|
||||||
|
continue 'poll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vmm.lock().unwrap().stop(EXIT_CODE_GENERIC_ERROR as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for all vCPUs to exit and terminates the Dragonball process.
|
||||||
|
fn stop(&mut self, exit_code: i32) -> i32 {
|
||||||
|
info!("Vmm is stopping.");
|
||||||
|
if let Some(vm) = self.get_vm_by_id_mut("") {
|
||||||
|
if vm.is_vm_initialized() {
|
||||||
|
if let Err(e) = vm.remove_devices() {
|
||||||
|
warn!("failed to remove devices: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = vm.reset_console() {
|
||||||
|
warn!("Cannot set canonical mode for the terminal. {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, we use exit_code instead of invoking _exit to
|
||||||
|
// terminate process, so all of vcpu threads should be stopped
|
||||||
|
// prior to vmm event loop.
|
||||||
|
match vm.vcpu_manager() {
|
||||||
|
Ok(mut mgr) => {
|
||||||
|
if let Err(e) = mgr.exit_all_vcpus() {
|
||||||
|
warn!("Failed to exit vcpu thread. {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => warn!("Failed to get vcpu manager {:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// save exit state to VM, instead of exit process.
|
||||||
|
vm.vm_exit(exit_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn create_vmm_instance() -> Vmm {
|
||||||
|
let info = Arc::new(RwLock::new(InstanceInfo::default()));
|
||||||
|
let event_fd = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
||||||
|
let seccomp_filter: BpfProgram = Vec::new();
|
||||||
|
let epoll_manager = EpollManager::default();
|
||||||
|
|
||||||
|
Vmm::new_with_epoll_manager(
|
||||||
|
info,
|
||||||
|
event_fd,
|
||||||
|
epoll_manager,
|
||||||
|
seccomp_filter.clone(),
|
||||||
|
seccomp_filter,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_vmm_instance() {
|
||||||
|
create_vmm_instance();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user