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::simple_api_full_command; 12 use api_client::Error as ApiClientError; 13 use clap::{Arg, ArgAction, ArgMatches, Command}; 14 use option_parser::{ByteSized, ByteSizedParseError}; 15 use std::fmt; 16 use std::io::Read; 17 use std::os::unix::net::UnixStream; 18 use std::process; 19 20 #[derive(Debug)] 21 enum Error { 22 Connect(std::io::Error), 23 ApiClient(ApiClientError), 24 InvalidCpuCount(std::num::ParseIntError), 25 InvalidMemorySize(ByteSizedParseError), 26 InvalidBalloonSize(ByteSizedParseError), 27 AddDeviceConfig(vmm::config::Error), 28 AddDiskConfig(vmm::config::Error), 29 AddFsConfig(vmm::config::Error), 30 AddPmemConfig(vmm::config::Error), 31 AddNetConfig(vmm::config::Error), 32 AddUserDeviceConfig(vmm::config::Error), 33 AddVdpaConfig(vmm::config::Error), 34 AddVsockConfig(vmm::config::Error), 35 Restore(vmm::config::Error), 36 ReadingStdin(std::io::Error), 37 ReadingFile(std::io::Error), 38 } 39 40 impl fmt::Display for Error { 41 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 use Error::*; 43 match self { 44 ApiClient(e) => e.fmt(f), 45 Connect(e) => write!(f, "Error opening HTTP socket: {e}"), 46 InvalidCpuCount(e) => write!(f, "Error parsing CPU count: {e}"), 47 InvalidMemorySize(e) => write!(f, "Error parsing memory size: {e:?}"), 48 InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {e:?}"), 49 AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {e}"), 50 AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {e}"), 51 AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {e}"), 52 AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {e}"), 53 AddNetConfig(e) => write!(f, "Error parsing network syntax: {e}"), 54 AddUserDeviceConfig(e) => write!(f, "Error parsing user device syntax: {e}"), 55 AddVdpaConfig(e) => write!(f, "Error parsing vDPA device syntax: {e}"), 56 AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {e}"), 57 Restore(e) => write!(f, "Error parsing restore syntax: {e}"), 58 ReadingStdin(e) => write!(f, "Error reading from stdin: {e}"), 59 ReadingFile(e) => write!(f, "Error reading from file: {e}"), 60 } 61 } 62 } 63 64 fn resize_api_command( 65 socket: &mut UnixStream, 66 cpus: Option<&str>, 67 memory: Option<&str>, 68 balloon: Option<&str>, 69 ) -> Result<(), Error> { 70 let desired_vcpus: Option<u8> = if let Some(cpus) = cpus { 71 Some(cpus.parse().map_err(Error::InvalidCpuCount)?) 72 } else { 73 None 74 }; 75 76 let desired_ram: Option<u64> = if let Some(memory) = memory { 77 Some( 78 memory 79 .parse::<ByteSized>() 80 .map_err(Error::InvalidMemorySize)? 81 .0, 82 ) 83 } else { 84 None 85 }; 86 87 let desired_balloon: Option<u64> = if let Some(balloon) = balloon { 88 Some( 89 balloon 90 .parse::<ByteSized>() 91 .map_err(Error::InvalidBalloonSize)? 92 .0, 93 ) 94 } else { 95 None 96 }; 97 98 let resize = vmm::api::VmResizeData { 99 desired_vcpus, 100 desired_ram, 101 desired_balloon, 102 }; 103 104 simple_api_command( 105 socket, 106 "PUT", 107 "resize", 108 Some(&serde_json::to_string(&resize).unwrap()), 109 ) 110 .map_err(Error::ApiClient) 111 } 112 113 fn resize_zone_api_command(socket: &mut UnixStream, id: &str, size: &str) -> Result<(), Error> { 114 let resize_zone = vmm::api::VmResizeZoneData { 115 id: id.to_owned(), 116 desired_ram: size 117 .parse::<ByteSized>() 118 .map_err(Error::InvalidMemorySize)? 119 .0, 120 }; 121 122 simple_api_command( 123 socket, 124 "PUT", 125 "resize-zone", 126 Some(&serde_json::to_string(&resize_zone).unwrap()), 127 ) 128 .map_err(Error::ApiClient) 129 } 130 131 fn add_device_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 132 let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?; 133 134 simple_api_command( 135 socket, 136 "PUT", 137 "add-device", 138 Some(&serde_json::to_string(&device_config).unwrap()), 139 ) 140 .map_err(Error::ApiClient) 141 } 142 143 fn add_user_device_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 144 let device_config = 145 vmm::config::UserDeviceConfig::parse(config).map_err(Error::AddUserDeviceConfig)?; 146 147 simple_api_command( 148 socket, 149 "PUT", 150 "add-user-device", 151 Some(&serde_json::to_string(&device_config).unwrap()), 152 ) 153 .map_err(Error::ApiClient) 154 } 155 156 fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Error> { 157 let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() }; 158 159 simple_api_command( 160 socket, 161 "PUT", 162 "remove-device", 163 Some(&serde_json::to_string(&remove_device_data).unwrap()), 164 ) 165 .map_err(Error::ApiClient) 166 } 167 168 fn add_disk_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 169 let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?; 170 171 simple_api_command( 172 socket, 173 "PUT", 174 "add-disk", 175 Some(&serde_json::to_string(&disk_config).unwrap()), 176 ) 177 .map_err(Error::ApiClient) 178 } 179 180 fn add_fs_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 181 let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?; 182 183 simple_api_command( 184 socket, 185 "PUT", 186 "add-fs", 187 Some(&serde_json::to_string(&fs_config).unwrap()), 188 ) 189 .map_err(Error::ApiClient) 190 } 191 192 fn add_pmem_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 193 let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; 194 195 simple_api_command( 196 socket, 197 "PUT", 198 "add-pmem", 199 Some(&serde_json::to_string(&pmem_config).unwrap()), 200 ) 201 .map_err(Error::ApiClient) 202 } 203 204 fn add_net_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 205 let mut net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?; 206 207 // NetConfig is modified on purpose here by taking the list of file 208 // descriptors out. Keeping the list and send it to the server side 209 // process would not make any sense since the file descriptor may be 210 // represented with different values. 211 let fds = net_config.fds.take().unwrap_or_default(); 212 213 simple_api_command_with_fds( 214 socket, 215 "PUT", 216 "add-net", 217 Some(&serde_json::to_string(&net_config).unwrap()), 218 fds, 219 ) 220 .map_err(Error::ApiClient) 221 } 222 223 fn add_vdpa_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 224 let vdpa_config = vmm::config::VdpaConfig::parse(config).map_err(Error::AddVdpaConfig)?; 225 226 simple_api_command( 227 socket, 228 "PUT", 229 "add-vdpa", 230 Some(&serde_json::to_string(&vdpa_config).unwrap()), 231 ) 232 .map_err(Error::ApiClient) 233 } 234 235 fn add_vsock_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 236 let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?; 237 238 simple_api_command( 239 socket, 240 "PUT", 241 "add-vsock", 242 Some(&serde_json::to_string(&vsock_config).unwrap()), 243 ) 244 .map_err(Error::ApiClient) 245 } 246 247 fn snapshot_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> { 248 let snapshot_config = vmm::api::VmSnapshotConfig { 249 destination_url: String::from(url), 250 }; 251 252 simple_api_command( 253 socket, 254 "PUT", 255 "snapshot", 256 Some(&serde_json::to_string(&snapshot_config).unwrap()), 257 ) 258 .map_err(Error::ApiClient) 259 } 260 261 fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> { 262 let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?; 263 264 simple_api_command( 265 socket, 266 "PUT", 267 "restore", 268 Some(&serde_json::to_string(&restore_config).unwrap()), 269 ) 270 .map_err(Error::ApiClient) 271 } 272 273 fn coredump_api_command(socket: &mut UnixStream, destination_url: &str) -> Result<(), Error> { 274 let coredump_config = vmm::api::VmCoredumpData { 275 destination_url: String::from(destination_url), 276 }; 277 278 simple_api_command( 279 socket, 280 "PUT", 281 "coredump", 282 Some(&serde_json::to_string(&coredump_config).unwrap()), 283 ) 284 .map_err(Error::ApiClient) 285 } 286 287 fn receive_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> { 288 let receive_migration_data = vmm::api::VmReceiveMigrationData { 289 receiver_url: url.to_owned(), 290 }; 291 simple_api_command( 292 socket, 293 "PUT", 294 "receive-migration", 295 Some(&serde_json::to_string(&receive_migration_data).unwrap()), 296 ) 297 .map_err(Error::ApiClient) 298 } 299 300 fn send_migration_api_command( 301 socket: &mut UnixStream, 302 url: &str, 303 local: bool, 304 ) -> Result<(), Error> { 305 let send_migration_data = vmm::api::VmSendMigrationData { 306 destination_url: url.to_owned(), 307 local, 308 }; 309 simple_api_command( 310 socket, 311 "PUT", 312 "send-migration", 313 Some(&serde_json::to_string(&send_migration_data).unwrap()), 314 ) 315 .map_err(Error::ApiClient) 316 } 317 318 fn create_api_command(socket: &mut UnixStream, path: &str) -> Result<(), Error> { 319 let mut data = String::default(); 320 if path == "-" { 321 std::io::stdin() 322 .read_to_string(&mut data) 323 .map_err(Error::ReadingStdin)?; 324 } else { 325 data = std::fs::read_to_string(path).map_err(Error::ReadingFile)?; 326 } 327 328 simple_api_command(socket, "PUT", "create", Some(&data)).map_err(Error::ApiClient) 329 } 330 331 fn do_command(matches: &ArgMatches) -> Result<(), Error> { 332 let mut socket = UnixStream::connect(matches.get_one::<String>("api-socket").unwrap()) 333 .map_err(Error::Connect)?; 334 335 match matches.subcommand_name() { 336 Some("info") => { 337 simple_api_command(&mut socket, "GET", "info", None).map_err(Error::ApiClient) 338 } 339 Some("counters") => { 340 simple_api_command(&mut socket, "GET", "counters", None).map_err(Error::ApiClient) 341 } 342 Some("ping") => { 343 simple_api_full_command(&mut socket, "GET", "vmm.ping", None).map_err(Error::ApiClient) 344 } 345 Some("shutdown-vmm") => simple_api_full_command(&mut socket, "PUT", "vmm.shutdown", None) 346 .map_err(Error::ApiClient), 347 Some("resize") => resize_api_command( 348 &mut socket, 349 matches 350 .subcommand_matches("resize") 351 .unwrap() 352 .get_one::<String>("cpus") 353 .map(|x| x as &str), 354 matches 355 .subcommand_matches("resize") 356 .unwrap() 357 .get_one::<String>("memory") 358 .map(|x| x as &str), 359 matches 360 .subcommand_matches("resize") 361 .unwrap() 362 .get_one::<String>("balloon") 363 .map(|x| x as &str), 364 ), 365 Some("resize-zone") => resize_zone_api_command( 366 &mut socket, 367 matches 368 .subcommand_matches("resize-zone") 369 .unwrap() 370 .get_one::<String>("id") 371 .unwrap(), 372 matches 373 .subcommand_matches("resize-zone") 374 .unwrap() 375 .get_one::<String>("size") 376 .unwrap(), 377 ), 378 Some("add-device") => add_device_api_command( 379 &mut socket, 380 matches 381 .subcommand_matches("add-device") 382 .unwrap() 383 .get_one::<String>("device_config") 384 .unwrap(), 385 ), 386 Some("remove-device") => remove_device_api_command( 387 &mut socket, 388 matches 389 .subcommand_matches("remove-device") 390 .unwrap() 391 .get_one::<String>("id") 392 .unwrap(), 393 ), 394 Some("add-disk") => add_disk_api_command( 395 &mut socket, 396 matches 397 .subcommand_matches("add-disk") 398 .unwrap() 399 .get_one::<String>("disk_config") 400 .unwrap(), 401 ), 402 Some("add-fs") => add_fs_api_command( 403 &mut socket, 404 matches 405 .subcommand_matches("add-fs") 406 .unwrap() 407 .get_one::<String>("fs_config") 408 .unwrap(), 409 ), 410 Some("add-pmem") => add_pmem_api_command( 411 &mut socket, 412 matches 413 .subcommand_matches("add-pmem") 414 .unwrap() 415 .get_one::<String>("pmem_config") 416 .unwrap(), 417 ), 418 Some("add-net") => add_net_api_command( 419 &mut socket, 420 matches 421 .subcommand_matches("add-net") 422 .unwrap() 423 .get_one::<String>("net_config") 424 .unwrap(), 425 ), 426 Some("add-user-device") => add_user_device_api_command( 427 &mut socket, 428 matches 429 .subcommand_matches("add-user-device") 430 .unwrap() 431 .get_one::<String>("device_config") 432 .unwrap(), 433 ), 434 Some("add-vdpa") => add_vdpa_api_command( 435 &mut socket, 436 matches 437 .subcommand_matches("add-vdpa") 438 .unwrap() 439 .get_one::<String>("vdpa_config") 440 .unwrap(), 441 ), 442 Some("add-vsock") => add_vsock_api_command( 443 &mut socket, 444 matches 445 .subcommand_matches("add-vsock") 446 .unwrap() 447 .get_one::<String>("vsock_config") 448 .unwrap(), 449 ), 450 Some("snapshot") => snapshot_api_command( 451 &mut socket, 452 matches 453 .subcommand_matches("snapshot") 454 .unwrap() 455 .get_one::<String>("snapshot_config") 456 .unwrap(), 457 ), 458 Some("restore") => restore_api_command( 459 &mut socket, 460 matches 461 .subcommand_matches("restore") 462 .unwrap() 463 .get_one::<String>("restore_config") 464 .unwrap(), 465 ), 466 Some("coredump") => coredump_api_command( 467 &mut socket, 468 matches 469 .subcommand_matches("coredump") 470 .unwrap() 471 .get_one::<String>("coredump_config") 472 .unwrap(), 473 ), 474 Some("send-migration") => send_migration_api_command( 475 &mut socket, 476 matches 477 .subcommand_matches("send-migration") 478 .unwrap() 479 .get_one::<String>("send_migration_config") 480 .unwrap(), 481 matches 482 .subcommand_matches("send-migration") 483 .unwrap() 484 .get_flag("send_migration_local"), 485 ), 486 Some("receive-migration") => receive_migration_api_command( 487 &mut socket, 488 matches 489 .subcommand_matches("receive-migration") 490 .unwrap() 491 .get_one::<String>("receive_migration_config") 492 .unwrap(), 493 ), 494 Some("create") => create_api_command( 495 &mut socket, 496 matches 497 .subcommand_matches("create") 498 .unwrap() 499 .get_one::<String>("path") 500 .unwrap(), 501 ), 502 Some(c) => simple_api_command(&mut socket, "PUT", c, None).map_err(Error::ApiClient), 503 None => unreachable!(), 504 } 505 } 506 507 fn main() { 508 let app = Command::new("ch-remote") 509 .author(crate_authors!()) 510 .subcommand_required(true) 511 .about("Remotely control a cloud-hypervisor VMM.") 512 .arg( 513 Arg::new("api-socket") 514 .long("api-socket") 515 .help("HTTP API socket path (UNIX domain socket).") 516 .num_args(1) 517 .required(true), 518 ) 519 .subcommand( 520 Command::new("add-device").about("Add VFIO device").arg( 521 Arg::new("device_config") 522 .index(1) 523 .help(vmm::config::DeviceConfig::SYNTAX), 524 ), 525 ) 526 .subcommand( 527 Command::new("add-disk").about("Add block device").arg( 528 Arg::new("disk_config") 529 .index(1) 530 .help(vmm::config::DiskConfig::SYNTAX), 531 ), 532 ) 533 .subcommand( 534 Command::new("add-fs") 535 .about("Add virtio-fs backed fs device") 536 .arg( 537 Arg::new("fs_config") 538 .index(1) 539 .help(vmm::config::FsConfig::SYNTAX), 540 ), 541 ) 542 .subcommand( 543 Command::new("add-pmem") 544 .about("Add persistent memory device") 545 .arg( 546 Arg::new("pmem_config") 547 .index(1) 548 .help(vmm::config::PmemConfig::SYNTAX), 549 ), 550 ) 551 .subcommand( 552 Command::new("add-net").about("Add network device").arg( 553 Arg::new("net_config") 554 .index(1) 555 .help(vmm::config::NetConfig::SYNTAX), 556 ), 557 ) 558 .subcommand( 559 Command::new("add-user-device") 560 .about("Add userspace device") 561 .arg( 562 Arg::new("device_config") 563 .index(1) 564 .help(vmm::config::UserDeviceConfig::SYNTAX), 565 ), 566 ) 567 .subcommand( 568 Command::new("add-vdpa").about("Add vDPA device").arg( 569 Arg::new("vdpa_config") 570 .index(1) 571 .help(vmm::config::VdpaConfig::SYNTAX), 572 ), 573 ) 574 .subcommand( 575 Command::new("add-vsock").about("Add vsock device").arg( 576 Arg::new("vsock_config") 577 .index(1) 578 .help(vmm::config::VsockConfig::SYNTAX), 579 ), 580 ) 581 .subcommand( 582 Command::new("remove-device") 583 .about("Remove VFIO device") 584 .arg(Arg::new("id").index(1).help("<device_id>")), 585 ) 586 .subcommand(Command::new("info").about("Info on the VM")) 587 .subcommand(Command::new("counters").about("Counters from the VM")) 588 .subcommand(Command::new("pause").about("Pause the VM")) 589 .subcommand(Command::new("reboot").about("Reboot the VM")) 590 .subcommand(Command::new("power-button").about("Trigger a power button in the VM")) 591 .subcommand( 592 Command::new("resize") 593 .about("Resize the VM") 594 .arg( 595 Arg::new("cpus") 596 .long("cpus") 597 .help("New vCPUs count") 598 .num_args(1), 599 ) 600 .arg( 601 Arg::new("memory") 602 .long("memory") 603 .help("New memory size in bytes (supports K/M/G suffix)") 604 .num_args(1), 605 ) 606 .arg( 607 Arg::new("balloon") 608 .long("balloon") 609 .help("New balloon size in bytes (supports K/M/G suffix)") 610 .num_args(1), 611 ), 612 ) 613 .subcommand( 614 Command::new("resize-zone") 615 .about("Resize a memory zone") 616 .arg( 617 Arg::new("id") 618 .long("id") 619 .help("Memory zone identifier") 620 .num_args(1), 621 ) 622 .arg( 623 Arg::new("size") 624 .long("size") 625 .help("New memory zone size in bytes (supports K/M/G suffix)") 626 .num_args(1), 627 ), 628 ) 629 .subcommand(Command::new("resume").about("Resume the VM")) 630 .subcommand(Command::new("boot").about("Boot a created VM")) 631 .subcommand(Command::new("delete").about("Delete a VM")) 632 .subcommand(Command::new("shutdown").about("Shutdown the VM")) 633 .subcommand( 634 Command::new("snapshot") 635 .about("Create a snapshot from VM") 636 .arg( 637 Arg::new("snapshot_config") 638 .index(1) 639 .help("<destination_url>"), 640 ), 641 ) 642 .subcommand( 643 Command::new("restore") 644 .about("Restore VM from a snapshot") 645 .arg( 646 Arg::new("restore_config") 647 .index(1) 648 .help(vmm::config::RestoreConfig::SYNTAX), 649 ), 650 ) 651 .subcommand( 652 Command::new("coredump") 653 .about("Create a coredump from VM") 654 .arg(Arg::new("coredump_config").index(1).help("<file_path>")), 655 ) 656 .subcommand( 657 Command::new("send-migration") 658 .about("Initiate a VM migration") 659 .arg( 660 Arg::new("send_migration_config") 661 .index(1) 662 .help("<destination_url>"), 663 ) 664 .arg( 665 Arg::new("send_migration_local") 666 .long("local") 667 .num_args(0) 668 .action(ArgAction::SetTrue), 669 ), 670 ) 671 .subcommand( 672 Command::new("receive-migration") 673 .about("Receive a VM migration") 674 .arg( 675 Arg::new("receive_migration_config") 676 .index(1) 677 .help("<receiver_url>"), 678 ), 679 ) 680 .subcommand( 681 Command::new("create") 682 .about("Create VM from a JSON configuration") 683 .arg(Arg::new("path").index(1).default_value("-")), 684 ) 685 .subcommand(Command::new("ping").about("Ping the VMM to check for API server availability")) 686 .subcommand(Command::new("shutdown-vmm").about("Shutdown the VMM")); 687 688 let matches = app.get_matches(); 689 690 if let Err(e) = do_command(&matches) { 691 eprintln!("Error running command: {e}"); 692 process::exit(1) 693 }; 694 } 695