Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cloud-hypervisor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ thiserror = { workspace = true }
tpm = { path = "../tpm" }
tracer = { path = "../tracer" }
vm-memory = { workspace = true }
vm-migration = { path = "../vm-migration" }
vmm = { path = "../vmm" }
vmm-sys-util = { workspace = true }
zbus = { version = "5.7.1", optional = true }
Expand Down
71 changes: 68 additions & 3 deletions cloud-hypervisor/src/bin/ch-remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ use std::num::NonZeroU32;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::process;
use std::thread::sleep;
use std::time::Duration;

use api_client::{
Error as ApiClientError, simple_api_command, simple_api_command_with_fds,
simple_api_full_command,
Error as ApiClientError, StatusCode, simple_api_command, simple_api_command_with_fds,
simple_api_full_command, simple_api_full_command_and_response,
};
use clap::{Arg, ArgAction, ArgMatches, Command};
use log::error;
use log::{error, info};
use option_parser::{ByteSized, ByteSizedParseError};
use thiserror::Error;
use vm_migration::progress::{MigrationProgress, MigrationState};
use vmm::config::RestoreConfig;
use vmm::vm_config::{
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig,
Expand Down Expand Up @@ -303,6 +306,8 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
Some("shutdown") => {
simple_api_command(socket, "PUT", "shutdown", None).map_err(Error::HttpApiClient)
}
Some("migration-progress") => simple_api_command(socket, "GET", "migration-progress", None)
.map_err(Error::HttpApiClient),
Some("nmi") => simple_api_command(socket, "PUT", "nmi", None).map_err(Error::HttpApiClient),
Some("resize") => {
let resize = resize_config(
Expand Down Expand Up @@ -522,8 +527,65 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
.unwrap()
.get_one::<PathBuf>("tls-dir")
.cloned(),
true,
);

simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data))
.map_err(Error::HttpApiClient)?;

// Wait for migration to finish
loop {
let response = simple_api_full_command_and_response(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want that? As far as I know ch-remote is just a tool that makes using the REST API easier, thus I would expect that it behaves like the REST API. If I understand correctly, you don't even give the user the possibility to not see the statistics etc. when using ch-remote?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would have to be discussed with upstream. I have a tendency to handle it this way, as it keep the blocking semantics in ch-remote.

Fair question, tho!

socket,
"GET",
"vm.migration-progress",
None,
)
.map_err(Error::HttpApiClient)?
// should have response
.ok_or(Error::HttpApiClient(ApiClientError::ServerResponse(
StatusCode::Ok,
None,
)))?;

assert_ne!(
response, "null",
"migration progress should be there immediately when the migration was dispatched"
);

let progress = serde_json::from_slice::<MigrationProgress>(response.as_bytes())
.map_err(|e| {
error!("failed to parse response as MigrationProgress: {e}");
Error::HttpApiClient(ApiClientError::ServerResponse(
StatusCode::Ok,
Some(response),
))
})?;

match progress.state {
MigrationState::Cancelled { .. } => {
info!("Migration was cancelled");
break;
}
MigrationState::Failed {
error_msg,
error_msg_debug,
} => {
error!("Migration failed! {error_msg}\n{error_msg_debug}");
break;
}
MigrationState::Finished { .. } => {
info!("Migration finished successfully");
break;
}
MigrationState::Ongoing { .. } => {
sleep(Duration::from_millis(50));
continue;
}
}
}

simple_api_full_command(socket, "PUT", "vmm.shutdown", None)
.map_err(Error::HttpApiClient)
}
Some("receive-migration") => {
Expand Down Expand Up @@ -963,6 +1025,7 @@ fn send_migration_data(
migration_timeout: u64,
connections: NonZeroU32,
tls_dir: Option<PathBuf>,
keep_alive: bool,
) -> String {
let send_migration_data = vmm::api::VmSendMigrationData {
destination_url: url,
Expand All @@ -971,6 +1034,7 @@ fn send_migration_data(
migration_timeout,
connections,
tls_dir,
keep_alive,
};

serde_json::to_string(&send_migration_data).unwrap()
Expand Down Expand Up @@ -1072,6 +1136,7 @@ fn get_cli_commands_sorted() -> Box<[Command]> {
.arg(Arg::new("path").index(1).default_value("-")),
Command::new("delete").about("Delete a VM"),
Command::new("info").about("Info on the VM"),
Command::new("migration-progress"),
Command::new("nmi").about("Trigger NMI"),
Command::new("pause").about("Pause the VM"),
Command::new("ping").about("Ping the VMM to check for API server availability"),
Expand Down
4 changes: 3 additions & 1 deletion vm-migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ use thiserror::Error;

use crate::protocol::MemoryRangeTable;

mod bitpos_iterator;
pub mod progress;
pub mod protocol;
pub mod tls;

mod bitpos_iterator;

#[derive(Error, Debug)]
pub enum MigratableError {
#[error("Failed to pause migratable component")]
Expand Down
Loading
Loading