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