1 // Copyright © 2020 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 #[macro_use(crate_authors)] 7 extern crate clap; 8 9 use api_client::simple_api_command; 10 use api_client::simple_api_command_with_fds; 11 use api_client::Error as ApiClientError; 12 use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; 13 use option_parser::{ByteSized, ByteSizedParseError}; 14 use std::fmt; 15 use std::os::unix::net::UnixStream; 16 use std::process; 17 18 #[derive(Debug)] 19 enum Error { 20 Connect(std::io::Error), 21 ApiClient(ApiClientError), 22 InvalidCpuCount(std::num::ParseIntError), 23 InvalidMemorySize(ByteSizedParseError), 24 InvalidBalloonSize(ByteSizedParseError), 25 AddDeviceConfig(vmm::config::Error), 26 AddDiskConfig(vmm::config::Error), 27 AddFsConfig(vmm::config::Error), 28 AddPmemConfig(vmm::config::Error), 29 AddNetConfig(vmm::config::Error), 30 AddVsockConfig(vmm::config::Error), 31 Restore(vmm::config::Error), 32 } 33 34 impl fmt::Display for Error { 35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 use Error::*; 37 match self { 38 ApiClient(e) => e.fmt(f), 39 Connect(e) => write!(f, "Error opening HTTP socket: {}", e), 40 InvalidCpuCount(e) => write!(f, "Error parsing CPU count: {}", e), 41 InvalidMemorySize(e) => write!(f, "Error parsing memory size: {:?}", e), 42 InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {:?}", e), 43 AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {}", e), 44 AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {}", e), 45 AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {}", e), 46 AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {}", e), 47 AddNetConfig(e) => write!(f, "Error parsing network syntax: {}", e), 48 AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {}", e), 49 Restore(e) => write!(f, "Error parsing restore syntax: {}", e), 50 } 51 } 52 } 53 54 fn resize_api_command( 55 socket: &mut UnixStream, 56 cpus: Option<&str>, 57 memory: Option<&str>, 58 balloon: Option<&str>, 59 ) -> Result<(), Error> { 60 let desired_vcpus: Option<u8> = if let Some(cpus) = cpus { 61 Some(cpus.parse().map_err(Error::InvalidCpuCount)?) 62 } else { 63 None 64 }; 65 66 let desired_ram: Option<u64> = if let Some(memory) = memory { 67 Some( 68 memory 69 .parse::<ByteSized>() 70 .map_err(Error::InvalidMemorySize)? 71 .0, 72 ) 73 } else { 74 None 75 }; 76 77 let desired_balloon: Option<u64> = if let Some(balloon) = balloon { 78 Some( 79 balloon 80 .parse::<ByteSized>() 81 .map_err(Error::InvalidBalloonSize)? 82 .0, 83 ) 84 } else { 85 None 86 }; 87 88 let resize = vmm::api::VmResizeData { 89 desired_vcpus, 90 desired_ram, 91 desired_balloon, 92 }; 93 94 simple_api_command( 95 socket, 96 "PUT", 97 "resize", 98 Some(&serde_json::to_string(&resize).unwrap()), 99 ) 100 .map_err(Error::ApiClient) 101 } 102 103 fn resize_zone_api_command(socket: &mut UnixStream, id: &str, size: &str) -> Result<(), Error> { 104 let resize_zone = vmm::api::VmResizeZoneData { 105 id: id.to_owned(), 106 desired_ram: size 107 .parse::<ByteSized>() 108 .map_err(Error::InvalidMemorySize)? 109 .0, 110 }; 111 112 simple_api_command( 113 socket, 114 "PUT", 115 "resize-zone", 116 Some(&serde_json::to_string(&resize_zone).unwrap()), 117 ) 118 .map_err(Error::ApiClient) 119 } 120 121 fn add_device_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 122 let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?; 123 124 simple_api_command( 125 socket, 126 "PUT", 127 "add-device", 128 Some(&serde_json::to_string(&device_config).unwrap()), 129 ) 130 .map_err(Error::ApiClient) 131 } 132 133 fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Error> { 134 let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() }; 135 136 simple_api_command( 137 socket, 138 "PUT", 139 "remove-device", 140 Some(&serde_json::to_string(&remove_device_data).unwrap()), 141 ) 142 .map_err(Error::ApiClient) 143 } 144 145 fn add_disk_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 146 let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?; 147 148 simple_api_command( 149 socket, 150 "PUT", 151 "add-disk", 152 Some(&serde_json::to_string(&disk_config).unwrap()), 153 ) 154 .map_err(Error::ApiClient) 155 } 156 157 fn add_fs_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 158 let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?; 159 160 simple_api_command( 161 socket, 162 "PUT", 163 "add-fs", 164 Some(&serde_json::to_string(&fs_config).unwrap()), 165 ) 166 .map_err(Error::ApiClient) 167 } 168 169 fn add_pmem_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 170 let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; 171 172 simple_api_command( 173 socket, 174 "PUT", 175 "add-pmem", 176 Some(&serde_json::to_string(&pmem_config).unwrap()), 177 ) 178 .map_err(Error::ApiClient) 179 } 180 181 fn add_net_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 182 let mut net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?; 183 184 // NetConfig is modified on purpose here by taking the list of file 185 // descriptors out. Keeping the list and send it to the server side 186 // process would not make any sense since the file descriptor may be 187 // represented with different values. 188 let fds = net_config.fds.take().unwrap_or_default(); 189 190 simple_api_command_with_fds( 191 socket, 192 "PUT", 193 "add-net", 194 Some(&serde_json::to_string(&net_config).unwrap()), 195 fds, 196 ) 197 .map_err(Error::ApiClient) 198 } 199 200 fn add_vsock_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 201 let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?; 202 203 simple_api_command( 204 socket, 205 "PUT", 206 "add-vsock", 207 Some(&serde_json::to_string(&vsock_config).unwrap()), 208 ) 209 .map_err(Error::ApiClient) 210 } 211 212 fn snapshot_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> { 213 let snapshot_config = vmm::api::VmSnapshotConfig { 214 destination_url: String::from(url), 215 }; 216 217 simple_api_command( 218 socket, 219 "PUT", 220 "snapshot", 221 Some(&serde_json::to_string(&snapshot_config).unwrap()), 222 ) 223 .map_err(Error::ApiClient) 224 } 225 226 fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 227 let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?; 228 229 simple_api_command( 230 socket, 231 "PUT", 232 "restore", 233 Some(&serde_json::to_string(&restore_config).unwrap()), 234 ) 235 .map_err(Error::ApiClient) 236 } 237 238 fn receive_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> { 239 let receive_migration_data = vmm::api::VmReceiveMigrationData { 240 receiver_url: url.to_owned(), 241 }; 242 simple_api_command( 243 socket, 244 "PUT", 245 "receive-migration", 246 Some(&serde_json::to_string(&receive_migration_data).unwrap()), 247 ) 248 .map_err(Error::ApiClient) 249 } 250 251 fn send_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> { 252 let send_migration_data = vmm::api::VmSendMigrationData { 253 destination_url: url.to_owned(), 254 }; 255 simple_api_command( 256 socket, 257 "PUT", 258 "send-migration", 259 Some(&serde_json::to_string(&send_migration_data).unwrap()), 260 ) 261 .map_err(Error::ApiClient) 262 } 263 264 fn do_command(matches: &ArgMatches) -> Result<(), Error> { 265 let mut socket = 266 UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Connect)?; 267 268 match matches.subcommand_name() { 269 Some("info") => { 270 simple_api_command(&mut socket, "GET", "info", None).map_err(Error::ApiClient) 271 } 272 Some("counters") => { 273 simple_api_command(&mut socket, "GET", "counters", None).map_err(Error::ApiClient) 274 } 275 Some("resize") => resize_api_command( 276 &mut socket, 277 matches 278 .subcommand_matches("resize") 279 .unwrap() 280 .value_of("cpus"), 281 matches 282 .subcommand_matches("resize") 283 .unwrap() 284 .value_of("memory"), 285 matches 286 .subcommand_matches("resize") 287 .unwrap() 288 .value_of("balloon"), 289 ), 290 Some("resize-zone") => resize_zone_api_command( 291 &mut socket, 292 matches 293 .subcommand_matches("resize-zone") 294 .unwrap() 295 .value_of("id") 296 .unwrap(), 297 matches 298 .subcommand_matches("resize-zone") 299 .unwrap() 300 .value_of("size") 301 .unwrap(), 302 ), 303 Some("add-device") => add_device_api_command( 304 &mut socket, 305 matches 306 .subcommand_matches("add-device") 307 .unwrap() 308 .value_of("device_config") 309 .unwrap(), 310 ), 311 Some("remove-device") => remove_device_api_command( 312 &mut socket, 313 matches 314 .subcommand_matches("remove-device") 315 .unwrap() 316 .value_of("id") 317 .unwrap(), 318 ), 319 Some("add-disk") => add_disk_api_command( 320 &mut socket, 321 matches 322 .subcommand_matches("add-disk") 323 .unwrap() 324 .value_of("disk_config") 325 .unwrap(), 326 ), 327 Some("add-fs") => add_fs_api_command( 328 &mut socket, 329 matches 330 .subcommand_matches("add-fs") 331 .unwrap() 332 .value_of("fs_config") 333 .unwrap(), 334 ), 335 Some("add-pmem") => add_pmem_api_command( 336 &mut socket, 337 matches 338 .subcommand_matches("add-pmem") 339 .unwrap() 340 .value_of("pmem_config") 341 .unwrap(), 342 ), 343 Some("add-net") => add_net_api_command( 344 &mut socket, 345 matches 346 .subcommand_matches("add-net") 347 .unwrap() 348 .value_of("net_config") 349 .unwrap(), 350 ), 351 Some("add-vsock") => add_vsock_api_command( 352 &mut socket, 353 matches 354 .subcommand_matches("add-vsock") 355 .unwrap() 356 .value_of("vsock_config") 357 .unwrap(), 358 ), 359 Some("snapshot") => snapshot_api_command( 360 &mut socket, 361 matches 362 .subcommand_matches("snapshot") 363 .unwrap() 364 .value_of("snapshot_config") 365 .unwrap(), 366 ), 367 Some("restore") => restore_api_command( 368 &mut socket, 369 matches 370 .subcommand_matches("restore") 371 .unwrap() 372 .value_of("restore_config") 373 .unwrap(), 374 ), 375 Some("send-migration") => send_migration_api_command( 376 &mut socket, 377 matches 378 .subcommand_matches("send-migration") 379 .unwrap() 380 .value_of("send_migration_config") 381 .unwrap(), 382 ), 383 Some("receive-migration") => receive_migration_api_command( 384 &mut socket, 385 matches 386 .subcommand_matches("receive-migration") 387 .unwrap() 388 .value_of("receive_migration_config") 389 .unwrap(), 390 ), 391 Some(c) => simple_api_command(&mut socket, "PUT", c, None).map_err(Error::ApiClient), 392 None => unreachable!(), 393 } 394 } 395 396 fn main() { 397 let app = App::new("ch-remote") 398 .author(crate_authors!()) 399 .setting(AppSettings::SubcommandRequired) 400 .about("Remotely control a cloud-hypervisor VMM.") 401 .arg( 402 Arg::with_name("api-socket") 403 .long("api-socket") 404 .help("HTTP API socket path (UNIX domain socket).") 405 .takes_value(true) 406 .number_of_values(1) 407 .required(true), 408 ) 409 .subcommand( 410 SubCommand::with_name("add-device") 411 .about("Add VFIO device") 412 .arg( 413 Arg::with_name("device_config") 414 .index(1) 415 .help(vmm::config::DeviceConfig::SYNTAX), 416 ), 417 ) 418 .subcommand( 419 SubCommand::with_name("add-disk") 420 .about("Add block device") 421 .arg( 422 Arg::with_name("disk_config") 423 .index(1) 424 .help(vmm::config::DiskConfig::SYNTAX), 425 ), 426 ) 427 .subcommand( 428 SubCommand::with_name("add-fs") 429 .about("Add virtio-fs backed fs device") 430 .arg( 431 Arg::with_name("fs_config") 432 .index(1) 433 .help(vmm::config::FsConfig::SYNTAX), 434 ), 435 ) 436 .subcommand( 437 SubCommand::with_name("add-pmem") 438 .about("Add persistent memory device") 439 .arg( 440 Arg::with_name("pmem_config") 441 .index(1) 442 .help(vmm::config::PmemConfig::SYNTAX), 443 ), 444 ) 445 .subcommand( 446 SubCommand::with_name("add-net") 447 .about("Add network device") 448 .arg( 449 Arg::with_name("net_config") 450 .index(1) 451 .help(vmm::config::NetConfig::SYNTAX), 452 ), 453 ) 454 .subcommand( 455 SubCommand::with_name("add-vsock") 456 .about("Add vsock device") 457 .arg( 458 Arg::with_name("vsock_config") 459 .index(1) 460 .help(vmm::config::VsockConfig::SYNTAX), 461 ), 462 ) 463 .subcommand( 464 SubCommand::with_name("remove-device") 465 .about("Remove VFIO device") 466 .arg(Arg::with_name("id").index(1).help("<device_id>")), 467 ) 468 .subcommand(SubCommand::with_name("info").about("Info on the VM")) 469 .subcommand(SubCommand::with_name("counters").about("Counters from the VM")) 470 .subcommand(SubCommand::with_name("pause").about("Pause the VM")) 471 .subcommand(SubCommand::with_name("reboot").about("Reboot the VM")) 472 .subcommand(SubCommand::with_name("power-button").about("Trigger a power button in the VM")) 473 .subcommand( 474 SubCommand::with_name("resize") 475 .about("Resize the VM") 476 .arg( 477 Arg::with_name("cpus") 478 .long("cpus") 479 .help("New vCPUs count") 480 .takes_value(true) 481 .number_of_values(1), 482 ) 483 .arg( 484 Arg::with_name("memory") 485 .long("memory") 486 .help("New memory size in bytes (supports K/M/G suffix)") 487 .takes_value(true) 488 .number_of_values(1), 489 ) 490 .arg( 491 Arg::with_name("balloon") 492 .long("balloon") 493 .help("New balloon size in bytes (supports K/M/G suffix)") 494 .takes_value(true) 495 .number_of_values(1), 496 ), 497 ) 498 .subcommand( 499 SubCommand::with_name("resize-zone") 500 .about("Resize a memory zone") 501 .arg( 502 Arg::with_name("id") 503 .long("id") 504 .help("Memory zone identifier") 505 .takes_value(true) 506 .number_of_values(1), 507 ) 508 .arg( 509 Arg::with_name("size") 510 .long("size") 511 .help("New memory zone size in bytes (supports K/M/G suffix)") 512 .takes_value(true) 513 .number_of_values(1), 514 ), 515 ) 516 .subcommand(SubCommand::with_name("resume").about("Resume the VM")) 517 .subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM")) 518 .subcommand( 519 SubCommand::with_name("snapshot") 520 .about("Create a snapshot from VM") 521 .arg( 522 Arg::with_name("snapshot_config") 523 .index(1) 524 .help("<destination_url>"), 525 ), 526 ) 527 .subcommand( 528 SubCommand::with_name("restore") 529 .about("Restore VM from a snapshot") 530 .arg( 531 Arg::with_name("restore_config") 532 .index(1) 533 .help(vmm::config::RestoreConfig::SYNTAX), 534 ), 535 ) 536 .subcommand( 537 SubCommand::with_name("send-migration") 538 .about("Initiate a VM migration") 539 .arg( 540 Arg::with_name("send_migration_config") 541 .index(1) 542 .help("<destination_url>"), 543 ), 544 ) 545 .subcommand( 546 SubCommand::with_name("receive-migration") 547 .about("Receive a VM migration") 548 .arg( 549 Arg::with_name("receive_migration_config") 550 .index(1) 551 .help("<receiver_url>"), 552 ), 553 ); 554 555 let matches = app.get_matches(); 556 557 if let Err(e) = do_command(&matches) { 558 eprintln!("Error running command: {}", e); 559 process::exit(1) 560 }; 561 } 562