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