1 // Copyright © 2020 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 use std::fmt; 7 use std::io::Read; 8 use std::marker::PhantomData; 9 use std::os::unix::net::UnixStream; 10 use std::process; 11 12 use api_client::simple_api_command; 13 use api_client::simple_api_command_with_fds; 14 use api_client::simple_api_full_command; 15 use api_client::Error as ApiClientError; 16 use clap::{Arg, ArgAction, ArgMatches, Command}; 17 use option_parser::{ByteSized, ByteSizedParseError}; 18 #[cfg(feature = "dbus_api")] 19 use zbus::{proxy, zvariant::Optional}; 20 21 type ApiResult = Result<(), Error>; 22 23 #[derive(Debug)] 24 enum Error { 25 HttpApiClient(ApiClientError), 26 #[cfg(feature = "dbus_api")] 27 DBusApiClient(zbus::Error), 28 InvalidCpuCount(std::num::ParseIntError), 29 InvalidMemorySize(ByteSizedParseError), 30 InvalidBalloonSize(ByteSizedParseError), 31 AddDeviceConfig(vmm::config::Error), 32 AddDiskConfig(vmm::config::Error), 33 AddFsConfig(vmm::config::Error), 34 AddPmemConfig(vmm::config::Error), 35 AddNetConfig(vmm::config::Error), 36 AddUserDeviceConfig(vmm::config::Error), 37 AddVdpaConfig(vmm::config::Error), 38 AddVsockConfig(vmm::config::Error), 39 Restore(vmm::config::Error), 40 ReadingStdin(std::io::Error), 41 ReadingFile(std::io::Error), 42 } 43 44 impl fmt::Display for Error { 45 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 use Error::*; 47 match self { 48 HttpApiClient(e) => e.fmt(f), 49 #[cfg(feature = "dbus_api")] 50 DBusApiClient(e) => write!(f, "Error D-Bus proxy: {e}"), 51 InvalidCpuCount(e) => write!(f, "Error parsing CPU count: {e}"), 52 InvalidMemorySize(e) => write!(f, "Error parsing memory size: {e:?}"), 53 InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {e:?}"), 54 AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {e}"), 55 AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {e}"), 56 AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {e}"), 57 AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {e}"), 58 AddNetConfig(e) => write!(f, "Error parsing network syntax: {e}"), 59 AddUserDeviceConfig(e) => write!(f, "Error parsing user device syntax: {e}"), 60 AddVdpaConfig(e) => write!(f, "Error parsing vDPA device syntax: {e}"), 61 AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {e}"), 62 Restore(e) => write!(f, "Error parsing restore syntax: {e}"), 63 ReadingStdin(e) => write!(f, "Error reading from stdin: {e}"), 64 ReadingFile(e) => write!(f, "Error reading from file: {e}"), 65 } 66 } 67 } 68 69 enum TargetApi<'a> { 70 HttpApi(UnixStream, PhantomData<&'a ()>), 71 #[cfg(feature = "dbus_api")] 72 DBusApi(DBusApi1ProxyBlocking<'a>), 73 } 74 75 #[cfg(feature = "dbus_api")] 76 #[proxy(name = "org.cloudhypervisor.DBusApi1", assume_defaults = false)] 77 trait DBusApi1 { 78 fn vmm_ping(&self) -> zbus::Result<String>; 79 fn vmm_shutdown(&self) -> zbus::Result<()>; 80 fn vm_add_device(&self, device_config: &str) -> zbus::Result<Optional<String>>; 81 fn vm_add_disk(&self, disk_config: &str) -> zbus::Result<Optional<String>>; 82 fn vm_add_fs(&self, fs_config: &str) -> zbus::Result<Optional<String>>; 83 fn vm_add_net(&self, net_config: &str) -> zbus::Result<Optional<String>>; 84 fn vm_add_pmem(&self, pmem_config: &str) -> zbus::Result<Optional<String>>; 85 fn vm_add_user_device(&self, vm_add_user_device: &str) -> zbus::Result<Optional<String>>; 86 fn vm_add_vdpa(&self, vdpa_config: &str) -> zbus::Result<Optional<String>>; 87 fn vm_add_vsock(&self, vsock_config: &str) -> zbus::Result<Optional<String>>; 88 fn vm_boot(&self) -> zbus::Result<()>; 89 fn vm_coredump(&self, vm_coredump_data: &str) -> zbus::Result<()>; 90 fn vm_counters(&self) -> zbus::Result<Optional<String>>; 91 fn vm_create(&self, vm_config: &str) -> zbus::Result<()>; 92 fn vm_delete(&self) -> zbus::Result<()>; 93 fn vm_info(&self) -> zbus::Result<String>; 94 fn vm_pause(&self) -> zbus::Result<()>; 95 fn vm_power_button(&self) -> zbus::Result<()>; 96 fn vm_reboot(&self) -> zbus::Result<()>; 97 fn vm_remove_device(&self, vm_remove_device: &str) -> zbus::Result<()>; 98 fn vm_resize(&self, vm_resize: &str) -> zbus::Result<()>; 99 fn vm_resize_zone(&self, vm_resize_zone: &str) -> zbus::Result<()>; 100 fn vm_restore(&self, restore_config: &str) -> zbus::Result<()>; 101 fn vm_receive_migration(&self, receive_migration_data: &str) -> zbus::Result<()>; 102 fn vm_send_migration(&self, receive_migration_data: &str) -> zbus::Result<()>; 103 fn vm_resume(&self) -> zbus::Result<()>; 104 fn vm_shutdown(&self) -> zbus::Result<()>; 105 fn vm_snapshot(&self, vm_snapshot_config: &str) -> zbus::Result<()>; 106 } 107 108 #[cfg(feature = "dbus_api")] 109 impl<'a> DBusApi1ProxyBlocking<'a> { 110 fn new_connection(name: &'a str, path: &'a str, system_bus: bool) -> Result<Self, zbus::Error> { 111 let connection = if system_bus { 112 zbus::blocking::Connection::system()? 113 } else { 114 zbus::blocking::Connection::session()? 115 }; 116 117 Self::builder(&connection) 118 .destination(name)? 119 .path(path)? 120 .build() 121 } 122 123 fn print_response(&self, result: zbus::Result<Optional<String>>) -> ApiResult { 124 result 125 .map(|ret| { 126 if let Some(ref output) = *ret { 127 println!("{output}"); 128 } 129 }) 130 .map_err(Error::DBusApiClient) 131 } 132 133 fn api_vmm_ping(&self) -> ApiResult { 134 self.vmm_ping() 135 .map(|ping| println!("{ping}")) 136 .map_err(Error::DBusApiClient) 137 } 138 139 fn api_vmm_shutdown(&self) -> ApiResult { 140 self.vmm_shutdown().map_err(Error::DBusApiClient) 141 } 142 143 fn api_vm_add_device(&self, device_config: &str) -> ApiResult { 144 self.print_response(self.vm_add_device(device_config)) 145 } 146 147 fn api_vm_add_disk(&self, disk_config: &str) -> ApiResult { 148 self.print_response(self.vm_add_disk(disk_config)) 149 } 150 151 fn api_vm_add_fs(&self, fs_config: &str) -> ApiResult { 152 self.print_response(self.vm_add_fs(fs_config)) 153 } 154 155 fn api_vm_add_net(&self, net_config: &str) -> ApiResult { 156 self.print_response(self.vm_add_net(net_config)) 157 } 158 159 fn api_vm_add_pmem(&self, pmem_config: &str) -> ApiResult { 160 self.print_response(self.vm_add_pmem(pmem_config)) 161 } 162 163 fn api_vm_add_user_device(&self, vm_add_user_device: &str) -> ApiResult { 164 self.print_response(self.vm_add_user_device(vm_add_user_device)) 165 } 166 167 fn api_vm_add_vdpa(&self, vdpa_config: &str) -> ApiResult { 168 self.print_response(self.vm_add_vdpa(vdpa_config)) 169 } 170 171 fn api_vm_add_vsock(&self, vsock_config: &str) -> ApiResult { 172 self.print_response(self.vm_add_vsock(vsock_config)) 173 } 174 175 fn api_vm_boot(&self) -> ApiResult { 176 self.vm_boot().map_err(Error::DBusApiClient) 177 } 178 179 fn api_vm_coredump(&self, vm_coredump_data: &str) -> ApiResult { 180 self.vm_coredump(vm_coredump_data) 181 .map_err(Error::DBusApiClient) 182 } 183 184 fn api_vm_counters(&self) -> ApiResult { 185 self.print_response(self.vm_counters()) 186 } 187 188 fn api_vm_create(&self, vm_config: &str) -> ApiResult { 189 self.vm_create(vm_config).map_err(Error::DBusApiClient) 190 } 191 192 fn api_vm_delete(&self) -> ApiResult { 193 self.vm_delete().map_err(Error::DBusApiClient) 194 } 195 196 fn api_vm_info(&self) -> ApiResult { 197 self.vm_info() 198 .map(|info| println!("{info}")) 199 .map_err(Error::DBusApiClient) 200 } 201 202 fn api_vm_pause(&self) -> ApiResult { 203 self.vm_pause().map_err(Error::DBusApiClient) 204 } 205 206 fn api_vm_power_button(&self) -> ApiResult { 207 self.vm_power_button().map_err(Error::DBusApiClient) 208 } 209 210 fn api_vm_reboot(&self) -> ApiResult { 211 self.vm_reboot().map_err(Error::DBusApiClient) 212 } 213 214 fn api_vm_remove_device(&self, vm_remove_device: &str) -> ApiResult { 215 self.vm_remove_device(vm_remove_device) 216 .map_err(Error::DBusApiClient) 217 } 218 219 fn api_vm_resize(&self, vm_resize: &str) -> ApiResult { 220 self.vm_resize(vm_resize).map_err(Error::DBusApiClient) 221 } 222 223 fn api_vm_resize_zone(&self, vm_resize_zone: &str) -> ApiResult { 224 self.vm_resize_zone(vm_resize_zone) 225 .map_err(Error::DBusApiClient) 226 } 227 228 fn api_vm_restore(&self, restore_config: &str) -> ApiResult { 229 self.vm_restore(restore_config) 230 .map_err(Error::DBusApiClient) 231 } 232 233 fn api_vm_receive_migration(&self, receive_migration_data: &str) -> ApiResult { 234 self.vm_receive_migration(receive_migration_data) 235 .map_err(Error::DBusApiClient) 236 } 237 238 fn api_vm_send_migration(&self, send_migration_data: &str) -> ApiResult { 239 self.vm_send_migration(send_migration_data) 240 .map_err(Error::DBusApiClient) 241 } 242 243 fn api_vm_resume(&self) -> ApiResult { 244 self.vm_resume().map_err(Error::DBusApiClient) 245 } 246 247 fn api_vm_shutdown(&self) -> ApiResult { 248 self.vm_shutdown().map_err(Error::DBusApiClient) 249 } 250 251 fn api_vm_snapshot(&self, vm_snapshot_config: &str) -> ApiResult { 252 self.vm_snapshot(vm_snapshot_config) 253 .map_err(Error::DBusApiClient) 254 } 255 } 256 257 impl<'a> TargetApi<'a> { 258 fn do_command(&mut self, matches: &ArgMatches) -> ApiResult { 259 match self { 260 Self::HttpApi(api_socket, _) => rest_api_do_command(matches, api_socket), 261 #[cfg(feature = "dbus_api")] 262 Self::DBusApi(proxy) => dbus_api_do_command(matches, proxy), 263 } 264 } 265 } 266 267 fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResult { 268 match matches.subcommand_name() { 269 Some("boot") => { 270 simple_api_command(socket, "PUT", "boot", None).map_err(Error::HttpApiClient) 271 } 272 Some("delete") => { 273 simple_api_command(socket, "PUT", "delete", None).map_err(Error::HttpApiClient) 274 } 275 Some("shutdown-vmm") => simple_api_full_command(socket, "PUT", "vmm.shutdown", None) 276 .map_err(Error::HttpApiClient), 277 Some("resume") => { 278 simple_api_command(socket, "PUT", "resume", None).map_err(Error::HttpApiClient) 279 } 280 Some("power-button") => { 281 simple_api_command(socket, "PUT", "power-button", None).map_err(Error::HttpApiClient) 282 } 283 Some("reboot") => { 284 simple_api_command(socket, "PUT", "reboot", None).map_err(Error::HttpApiClient) 285 } 286 Some("pause") => { 287 simple_api_command(socket, "PUT", "pause", None).map_err(Error::HttpApiClient) 288 } 289 Some("info") => { 290 simple_api_command(socket, "GET", "info", None).map_err(Error::HttpApiClient) 291 } 292 Some("counters") => { 293 simple_api_command(socket, "GET", "counters", None).map_err(Error::HttpApiClient) 294 } 295 Some("ping") => { 296 simple_api_full_command(socket, "GET", "vmm.ping", None).map_err(Error::HttpApiClient) 297 } 298 Some("shutdown") => { 299 simple_api_command(socket, "PUT", "shutdown", None).map_err(Error::HttpApiClient) 300 } 301 Some("nmi") => simple_api_command(socket, "PUT", "nmi", None).map_err(Error::HttpApiClient), 302 Some("resize") => { 303 let resize = resize_config( 304 matches 305 .subcommand_matches("resize") 306 .unwrap() 307 .get_one::<String>("cpus") 308 .map(|x| x as &str), 309 matches 310 .subcommand_matches("resize") 311 .unwrap() 312 .get_one::<String>("memory") 313 .map(|x| x as &str), 314 matches 315 .subcommand_matches("resize") 316 .unwrap() 317 .get_one::<String>("balloon") 318 .map(|x| x as &str), 319 )?; 320 simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient) 321 } 322 Some("resize-zone") => { 323 let resize_zone = resize_zone_config( 324 matches 325 .subcommand_matches("resize-zone") 326 .unwrap() 327 .get_one::<String>("id") 328 .unwrap(), 329 matches 330 .subcommand_matches("resize-zone") 331 .unwrap() 332 .get_one::<String>("size") 333 .unwrap(), 334 )?; 335 simple_api_command(socket, "PUT", "resize-zone", Some(&resize_zone)) 336 .map_err(Error::HttpApiClient) 337 } 338 Some("add-device") => { 339 let device_config = add_device_config( 340 matches 341 .subcommand_matches("add-device") 342 .unwrap() 343 .get_one::<String>("device_config") 344 .unwrap(), 345 )?; 346 simple_api_command(socket, "PUT", "add-device", Some(&device_config)) 347 .map_err(Error::HttpApiClient) 348 } 349 Some("remove-device") => { 350 let remove_device_data = remove_device_config( 351 matches 352 .subcommand_matches("remove-device") 353 .unwrap() 354 .get_one::<String>("id") 355 .unwrap(), 356 ); 357 simple_api_command(socket, "PUT", "remove-device", Some(&remove_device_data)) 358 .map_err(Error::HttpApiClient) 359 } 360 Some("add-disk") => { 361 let disk_config = add_disk_config( 362 matches 363 .subcommand_matches("add-disk") 364 .unwrap() 365 .get_one::<String>("disk_config") 366 .unwrap(), 367 )?; 368 simple_api_command(socket, "PUT", "add-disk", Some(&disk_config)) 369 .map_err(Error::HttpApiClient) 370 } 371 Some("add-fs") => { 372 let fs_config = add_fs_config( 373 matches 374 .subcommand_matches("add-fs") 375 .unwrap() 376 .get_one::<String>("fs_config") 377 .unwrap(), 378 )?; 379 simple_api_command(socket, "PUT", "add-fs", Some(&fs_config)) 380 .map_err(Error::HttpApiClient) 381 } 382 Some("add-pmem") => { 383 let pmem_config = add_pmem_config( 384 matches 385 .subcommand_matches("add-pmem") 386 .unwrap() 387 .get_one::<String>("pmem_config") 388 .unwrap(), 389 )?; 390 simple_api_command(socket, "PUT", "add-pmem", Some(&pmem_config)) 391 .map_err(Error::HttpApiClient) 392 } 393 Some("add-net") => { 394 let (net_config, fds) = add_net_config( 395 matches 396 .subcommand_matches("add-net") 397 .unwrap() 398 .get_one::<String>("net_config") 399 .unwrap(), 400 )?; 401 simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), fds) 402 .map_err(Error::HttpApiClient) 403 } 404 Some("add-user-device") => { 405 let device_config = add_user_device_config( 406 matches 407 .subcommand_matches("add-user-device") 408 .unwrap() 409 .get_one::<String>("device_config") 410 .unwrap(), 411 )?; 412 simple_api_command(socket, "PUT", "add-user-device", Some(&device_config)) 413 .map_err(Error::HttpApiClient) 414 } 415 Some("add-vdpa") => { 416 let vdpa_config = add_vdpa_config( 417 matches 418 .subcommand_matches("add-vdpa") 419 .unwrap() 420 .get_one::<String>("vdpa_config") 421 .unwrap(), 422 )?; 423 simple_api_command(socket, "PUT", "add-vdpa", Some(&vdpa_config)) 424 .map_err(Error::HttpApiClient) 425 } 426 Some("add-vsock") => { 427 let vsock_config = add_vsock_config( 428 matches 429 .subcommand_matches("add-vsock") 430 .unwrap() 431 .get_one::<String>("vsock_config") 432 .unwrap(), 433 )?; 434 simple_api_command(socket, "PUT", "add-vsock", Some(&vsock_config)) 435 .map_err(Error::HttpApiClient) 436 } 437 Some("snapshot") => { 438 let snapshot_config = snapshot_config( 439 matches 440 .subcommand_matches("snapshot") 441 .unwrap() 442 .get_one::<String>("snapshot_config") 443 .unwrap(), 444 ); 445 simple_api_command(socket, "PUT", "snapshot", Some(&snapshot_config)) 446 .map_err(Error::HttpApiClient) 447 } 448 Some("restore") => { 449 let (restore_config, fds) = restore_config( 450 matches 451 .subcommand_matches("restore") 452 .unwrap() 453 .get_one::<String>("restore_config") 454 .unwrap(), 455 )?; 456 simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), fds) 457 .map_err(Error::HttpApiClient) 458 } 459 Some("coredump") => { 460 let coredump_config = coredump_config( 461 matches 462 .subcommand_matches("coredump") 463 .unwrap() 464 .get_one::<String>("coredump_config") 465 .unwrap(), 466 ); 467 simple_api_command(socket, "PUT", "coredump", Some(&coredump_config)) 468 .map_err(Error::HttpApiClient) 469 } 470 Some("send-migration") => { 471 let send_migration_data = send_migration_data( 472 matches 473 .subcommand_matches("send-migration") 474 .unwrap() 475 .get_one::<String>("send_migration_config") 476 .unwrap(), 477 matches 478 .subcommand_matches("send-migration") 479 .unwrap() 480 .get_flag("send_migration_local"), 481 ); 482 simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data)) 483 .map_err(Error::HttpApiClient) 484 } 485 Some("receive-migration") => { 486 let receive_migration_data = receive_migration_data( 487 matches 488 .subcommand_matches("receive-migration") 489 .unwrap() 490 .get_one::<String>("receive_migration_config") 491 .unwrap(), 492 ); 493 simple_api_command( 494 socket, 495 "PUT", 496 "receive-migration", 497 Some(&receive_migration_data), 498 ) 499 .map_err(Error::HttpApiClient) 500 } 501 Some("create") => { 502 let data = create_data( 503 matches 504 .subcommand_matches("create") 505 .unwrap() 506 .get_one::<String>("path") 507 .unwrap(), 508 )?; 509 simple_api_command(socket, "PUT", "create", Some(&data)).map_err(Error::HttpApiClient) 510 } 511 _ => unreachable!(), 512 } 513 } 514 515 #[cfg(feature = "dbus_api")] 516 fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>) -> ApiResult { 517 match matches.subcommand_name() { 518 Some("boot") => proxy.api_vm_boot(), 519 Some("delete") => proxy.api_vm_delete(), 520 Some("shutdown-vmm") => proxy.api_vmm_shutdown(), 521 Some("resume") => proxy.api_vm_resume(), 522 Some("power-button") => proxy.api_vm_power_button(), 523 Some("reboot") => proxy.api_vm_reboot(), 524 Some("pause") => proxy.api_vm_pause(), 525 Some("info") => proxy.api_vm_info(), 526 Some("counters") => proxy.api_vm_counters(), 527 Some("ping") => proxy.api_vmm_ping(), 528 Some("shutdown") => proxy.api_vm_shutdown(), 529 Some("resize") => { 530 let resize = resize_config( 531 matches 532 .subcommand_matches("resize") 533 .unwrap() 534 .get_one::<String>("cpus") 535 .map(|x| x as &str), 536 matches 537 .subcommand_matches("resize") 538 .unwrap() 539 .get_one::<String>("memory") 540 .map(|x| x as &str), 541 matches 542 .subcommand_matches("resize") 543 .unwrap() 544 .get_one::<String>("balloon") 545 .map(|x| x as &str), 546 )?; 547 proxy.api_vm_resize(&resize) 548 } 549 Some("resize-zone") => { 550 let resize_zone = resize_zone_config( 551 matches 552 .subcommand_matches("resize-zone") 553 .unwrap() 554 .get_one::<String>("id") 555 .unwrap(), 556 matches 557 .subcommand_matches("resize-zone") 558 .unwrap() 559 .get_one::<String>("size") 560 .unwrap(), 561 )?; 562 proxy.api_vm_resize_zone(&resize_zone) 563 } 564 Some("add-device") => { 565 let device_config = add_device_config( 566 matches 567 .subcommand_matches("add-device") 568 .unwrap() 569 .get_one::<String>("device_config") 570 .unwrap(), 571 )?; 572 proxy.api_vm_add_device(&device_config) 573 } 574 Some("remove-device") => { 575 let remove_device_data = remove_device_config( 576 matches 577 .subcommand_matches("remove-device") 578 .unwrap() 579 .get_one::<String>("id") 580 .unwrap(), 581 ); 582 proxy.api_vm_remove_device(&remove_device_data) 583 } 584 Some("add-disk") => { 585 let disk_config = add_disk_config( 586 matches 587 .subcommand_matches("add-disk") 588 .unwrap() 589 .get_one::<String>("disk_config") 590 .unwrap(), 591 )?; 592 proxy.api_vm_add_disk(&disk_config) 593 } 594 Some("add-fs") => { 595 let fs_config = add_fs_config( 596 matches 597 .subcommand_matches("add-fs") 598 .unwrap() 599 .get_one::<String>("fs_config") 600 .unwrap(), 601 )?; 602 proxy.api_vm_add_fs(&fs_config) 603 } 604 Some("add-pmem") => { 605 let pmem_config = add_pmem_config( 606 matches 607 .subcommand_matches("add-pmem") 608 .unwrap() 609 .get_one::<String>("pmem_config") 610 .unwrap(), 611 )?; 612 proxy.api_vm_add_pmem(&pmem_config) 613 } 614 Some("add-net") => { 615 let (net_config, _fds) = add_net_config( 616 matches 617 .subcommand_matches("add-net") 618 .unwrap() 619 .get_one::<String>("net_config") 620 .unwrap(), 621 )?; 622 proxy.api_vm_add_net(&net_config) 623 } 624 Some("add-user-device") => { 625 let device_config = add_user_device_config( 626 matches 627 .subcommand_matches("add-user-device") 628 .unwrap() 629 .get_one::<String>("device_config") 630 .unwrap(), 631 )?; 632 proxy.api_vm_add_user_device(&device_config) 633 } 634 Some("add-vdpa") => { 635 let vdpa_config = add_vdpa_config( 636 matches 637 .subcommand_matches("add-vdpa") 638 .unwrap() 639 .get_one::<String>("vdpa_config") 640 .unwrap(), 641 )?; 642 proxy.api_vm_add_vdpa(&vdpa_config) 643 } 644 Some("add-vsock") => { 645 let vsock_config = add_vsock_config( 646 matches 647 .subcommand_matches("add-vsock") 648 .unwrap() 649 .get_one::<String>("vsock_config") 650 .unwrap(), 651 )?; 652 proxy.api_vm_add_vsock(&vsock_config) 653 } 654 Some("snapshot") => { 655 let snapshot_config = snapshot_config( 656 matches 657 .subcommand_matches("snapshot") 658 .unwrap() 659 .get_one::<String>("snapshot_config") 660 .unwrap(), 661 ); 662 proxy.api_vm_snapshot(&snapshot_config) 663 } 664 Some("restore") => { 665 let (restore_config, _fds) = restore_config( 666 matches 667 .subcommand_matches("restore") 668 .unwrap() 669 .get_one::<String>("restore_config") 670 .unwrap(), 671 )?; 672 proxy.api_vm_restore(&restore_config) 673 } 674 Some("coredump") => { 675 let coredump_config = coredump_config( 676 matches 677 .subcommand_matches("coredump") 678 .unwrap() 679 .get_one::<String>("coredump_config") 680 .unwrap(), 681 ); 682 proxy.api_vm_coredump(&coredump_config) 683 } 684 Some("send-migration") => { 685 let send_migration_data = send_migration_data( 686 matches 687 .subcommand_matches("send-migration") 688 .unwrap() 689 .get_one::<String>("send_migration_config") 690 .unwrap(), 691 matches 692 .subcommand_matches("send-migration") 693 .unwrap() 694 .get_flag("send_migration_local"), 695 ); 696 proxy.api_vm_send_migration(&send_migration_data) 697 } 698 Some("receive-migration") => { 699 let receive_migration_data = receive_migration_data( 700 matches 701 .subcommand_matches("receive-migration") 702 .unwrap() 703 .get_one::<String>("receive_migration_config") 704 .unwrap(), 705 ); 706 proxy.api_vm_receive_migration(&receive_migration_data) 707 } 708 Some("create") => { 709 let data = create_data( 710 matches 711 .subcommand_matches("create") 712 .unwrap() 713 .get_one::<String>("path") 714 .unwrap(), 715 )?; 716 proxy.api_vm_create(&data) 717 } 718 _ => unreachable!(), 719 } 720 } 721 722 fn resize_config( 723 cpus: Option<&str>, 724 memory: Option<&str>, 725 balloon: Option<&str>, 726 ) -> Result<String, Error> { 727 let desired_vcpus: Option<u8> = if let Some(cpus) = cpus { 728 Some(cpus.parse().map_err(Error::InvalidCpuCount)?) 729 } else { 730 None 731 }; 732 733 let desired_ram: Option<u64> = if let Some(memory) = memory { 734 Some( 735 memory 736 .parse::<ByteSized>() 737 .map_err(Error::InvalidMemorySize)? 738 .0, 739 ) 740 } else { 741 None 742 }; 743 744 let desired_balloon: Option<u64> = if let Some(balloon) = balloon { 745 Some( 746 balloon 747 .parse::<ByteSized>() 748 .map_err(Error::InvalidBalloonSize)? 749 .0, 750 ) 751 } else { 752 None 753 }; 754 755 let resize = vmm::api::VmResizeData { 756 desired_vcpus, 757 desired_ram, 758 desired_balloon, 759 }; 760 761 Ok(serde_json::to_string(&resize).unwrap()) 762 } 763 764 fn resize_zone_config(id: &str, size: &str) -> Result<String, Error> { 765 let resize_zone = vmm::api::VmResizeZoneData { 766 id: id.to_owned(), 767 desired_ram: size 768 .parse::<ByteSized>() 769 .map_err(Error::InvalidMemorySize)? 770 .0, 771 }; 772 773 Ok(serde_json::to_string(&resize_zone).unwrap()) 774 } 775 776 fn add_device_config(config: &str) -> Result<String, Error> { 777 let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?; 778 let device_config = serde_json::to_string(&device_config).unwrap(); 779 780 Ok(device_config) 781 } 782 783 fn add_user_device_config(config: &str) -> Result<String, Error> { 784 let device_config = 785 vmm::config::UserDeviceConfig::parse(config).map_err(Error::AddUserDeviceConfig)?; 786 let device_config = serde_json::to_string(&device_config).unwrap(); 787 788 Ok(device_config) 789 } 790 791 fn remove_device_config(id: &str) -> String { 792 let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() }; 793 794 serde_json::to_string(&remove_device_data).unwrap() 795 } 796 797 fn add_disk_config(config: &str) -> Result<String, Error> { 798 let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?; 799 let disk_config = serde_json::to_string(&disk_config).unwrap(); 800 801 Ok(disk_config) 802 } 803 804 fn add_fs_config(config: &str) -> Result<String, Error> { 805 let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?; 806 let fs_config = serde_json::to_string(&fs_config).unwrap(); 807 808 Ok(fs_config) 809 } 810 811 fn add_pmem_config(config: &str) -> Result<String, Error> { 812 let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; 813 let pmem_config = serde_json::to_string(&pmem_config).unwrap(); 814 815 Ok(pmem_config) 816 } 817 818 fn add_net_config(config: &str) -> Result<(String, Vec<i32>), Error> { 819 let mut net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?; 820 821 // NetConfig is modified on purpose here by taking the list of file 822 // descriptors out. Keeping the list and send it to the server side 823 // process would not make any sense since the file descriptor may be 824 // represented with different values. 825 let fds = net_config.fds.take().unwrap_or_default(); 826 let net_config = serde_json::to_string(&net_config).unwrap(); 827 828 Ok((net_config, fds)) 829 } 830 831 fn add_vdpa_config(config: &str) -> Result<String, Error> { 832 let vdpa_config = vmm::config::VdpaConfig::parse(config).map_err(Error::AddVdpaConfig)?; 833 let vdpa_config = serde_json::to_string(&vdpa_config).unwrap(); 834 835 Ok(vdpa_config) 836 } 837 838 fn add_vsock_config(config: &str) -> Result<String, Error> { 839 let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?; 840 let vsock_config = serde_json::to_string(&vsock_config).unwrap(); 841 842 Ok(vsock_config) 843 } 844 845 fn snapshot_config(url: &str) -> String { 846 let snapshot_config = vmm::api::VmSnapshotConfig { 847 destination_url: String::from(url), 848 }; 849 850 serde_json::to_string(&snapshot_config).unwrap() 851 } 852 853 fn restore_config(config: &str) -> Result<(String, Vec<i32>), Error> { 854 let mut restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?; 855 // RestoreConfig is modified on purpose to take out the file descriptors. 856 // These fds are passed to the server side process via SCM_RIGHTS 857 let fds = match &mut restore_config.net_fds { 858 Some(net_fds) => net_fds 859 .iter_mut() 860 .flat_map(|net| net.fds.take().unwrap_or_default()) 861 .collect(), 862 None => Vec::new(), 863 }; 864 let restore_config = serde_json::to_string(&restore_config).unwrap(); 865 866 Ok((restore_config, fds)) 867 } 868 869 fn coredump_config(destination_url: &str) -> String { 870 let coredump_config = vmm::api::VmCoredumpData { 871 destination_url: String::from(destination_url), 872 }; 873 874 serde_json::to_string(&coredump_config).unwrap() 875 } 876 877 fn receive_migration_data(url: &str) -> String { 878 let receive_migration_data = vmm::api::VmReceiveMigrationData { 879 receiver_url: url.to_owned(), 880 }; 881 882 serde_json::to_string(&receive_migration_data).unwrap() 883 } 884 885 fn send_migration_data(url: &str, local: bool) -> String { 886 let send_migration_data = vmm::api::VmSendMigrationData { 887 destination_url: url.to_owned(), 888 local, 889 }; 890 891 serde_json::to_string(&send_migration_data).unwrap() 892 } 893 894 fn create_data(path: &str) -> Result<String, Error> { 895 let mut data = String::default(); 896 if path == "-" { 897 std::io::stdin() 898 .read_to_string(&mut data) 899 .map_err(Error::ReadingStdin)?; 900 } else { 901 data = std::fs::read_to_string(path).map_err(Error::ReadingFile)?; 902 } 903 904 Ok(data) 905 } 906 907 fn main() { 908 let app = Command::new("ch-remote") 909 .author(env!("CARGO_PKG_AUTHORS")) 910 .version(env!("BUILD_VERSION")) 911 .about("Remotely control a cloud-hypervisor VMM.") 912 .arg_required_else_help(true) 913 .subcommand_required(true) 914 .args([ 915 Arg::new("api-socket") 916 .long("api-socket") 917 .help("HTTP API socket path (UNIX domain socket).") 918 .num_args(1), 919 #[cfg(feature = "dbus_api")] 920 Arg::new("dbus-service-name") 921 .long("dbus-service-name") 922 .help("Well known name of the dbus service") 923 .num_args(1), 924 #[cfg(feature = "dbus_api")] 925 Arg::new("dbus-object-path") 926 .long("dbus-object-path") 927 .help("Object path which the interface is being served at") 928 .num_args(1), 929 #[cfg(feature = "dbus_api")] 930 Arg::new("dbus-system-bus") 931 .long("dbus-system-bus") 932 .action(ArgAction::SetTrue) 933 .num_args(0) 934 .help("Use the system bus instead of a session bus"), 935 ]) 936 .subcommand( 937 Command::new("add-device").about("Add VFIO device").arg( 938 Arg::new("device_config") 939 .index(1) 940 .help(vmm::config::DeviceConfig::SYNTAX), 941 ), 942 ) 943 .subcommand( 944 Command::new("add-disk").about("Add block device").arg( 945 Arg::new("disk_config") 946 .index(1) 947 .help(vmm::config::DiskConfig::SYNTAX), 948 ), 949 ) 950 .subcommand( 951 Command::new("add-fs") 952 .about("Add virtio-fs backed fs device") 953 .arg( 954 Arg::new("fs_config") 955 .index(1) 956 .help(vmm::config::FsConfig::SYNTAX), 957 ), 958 ) 959 .subcommand( 960 Command::new("add-pmem") 961 .about("Add persistent memory device") 962 .arg( 963 Arg::new("pmem_config") 964 .index(1) 965 .help(vmm::config::PmemConfig::SYNTAX), 966 ), 967 ) 968 .subcommand( 969 Command::new("add-net").about("Add network device").arg( 970 Arg::new("net_config") 971 .index(1) 972 .help(vmm::config::NetConfig::SYNTAX), 973 ), 974 ) 975 .subcommand( 976 Command::new("add-user-device") 977 .about("Add userspace device") 978 .arg( 979 Arg::new("device_config") 980 .index(1) 981 .help(vmm::config::UserDeviceConfig::SYNTAX), 982 ), 983 ) 984 .subcommand( 985 Command::new("add-vdpa").about("Add vDPA device").arg( 986 Arg::new("vdpa_config") 987 .index(1) 988 .help(vmm::config::VdpaConfig::SYNTAX), 989 ), 990 ) 991 .subcommand( 992 Command::new("add-vsock").about("Add vsock device").arg( 993 Arg::new("vsock_config") 994 .index(1) 995 .help(vmm::config::VsockConfig::SYNTAX), 996 ), 997 ) 998 .subcommand( 999 Command::new("remove-device") 1000 .about("Remove VFIO and PCI device") 1001 .arg(Arg::new("id").index(1).help("<device_id>")), 1002 ) 1003 .subcommand(Command::new("info").about("Info on the VM")) 1004 .subcommand(Command::new("counters").about("Counters from the VM")) 1005 .subcommand(Command::new("pause").about("Pause the VM")) 1006 .subcommand(Command::new("reboot").about("Reboot the VM")) 1007 .subcommand(Command::new("power-button").about("Trigger a power button in the VM")) 1008 .subcommand( 1009 Command::new("resize") 1010 .about("Resize the VM") 1011 .arg( 1012 Arg::new("cpus") 1013 .long("cpus") 1014 .help("New vCPUs count") 1015 .num_args(1), 1016 ) 1017 .arg( 1018 Arg::new("memory") 1019 .long("memory") 1020 .help("New memory size in bytes (supports K/M/G suffix)") 1021 .num_args(1), 1022 ) 1023 .arg( 1024 Arg::new("balloon") 1025 .long("balloon") 1026 .help("New balloon size in bytes (supports K/M/G suffix)") 1027 .num_args(1), 1028 ), 1029 ) 1030 .subcommand( 1031 Command::new("resize-zone") 1032 .about("Resize a memory zone") 1033 .arg( 1034 Arg::new("id") 1035 .long("id") 1036 .help("Memory zone identifier") 1037 .num_args(1), 1038 ) 1039 .arg( 1040 Arg::new("size") 1041 .long("size") 1042 .help("New memory zone size in bytes (supports K/M/G suffix)") 1043 .num_args(1), 1044 ), 1045 ) 1046 .subcommand(Command::new("resume").about("Resume the VM")) 1047 .subcommand(Command::new("boot").about("Boot a created VM")) 1048 .subcommand(Command::new("delete").about("Delete a VM")) 1049 .subcommand(Command::new("shutdown").about("Shutdown the VM")) 1050 .subcommand( 1051 Command::new("snapshot") 1052 .about("Create a snapshot from VM") 1053 .arg( 1054 Arg::new("snapshot_config") 1055 .index(1) 1056 .help("<destination_url>"), 1057 ), 1058 ) 1059 .subcommand( 1060 Command::new("restore") 1061 .about("Restore VM from a snapshot") 1062 .arg( 1063 Arg::new("restore_config") 1064 .index(1) 1065 .help(vmm::config::RestoreConfig::SYNTAX), 1066 ), 1067 ) 1068 .subcommand( 1069 Command::new("coredump") 1070 .about("Create a coredump from VM") 1071 .arg(Arg::new("coredump_config").index(1).help("<file_path>")), 1072 ) 1073 .subcommand( 1074 Command::new("send-migration") 1075 .about("Initiate a VM migration") 1076 .arg( 1077 Arg::new("send_migration_config") 1078 .index(1) 1079 .help("<destination_url>"), 1080 ) 1081 .arg( 1082 Arg::new("send_migration_local") 1083 .long("local") 1084 .num_args(0) 1085 .action(ArgAction::SetTrue), 1086 ), 1087 ) 1088 .subcommand( 1089 Command::new("receive-migration") 1090 .about("Receive a VM migration") 1091 .arg( 1092 Arg::new("receive_migration_config") 1093 .index(1) 1094 .help("<receiver_url>"), 1095 ), 1096 ) 1097 .subcommand( 1098 Command::new("create") 1099 .about("Create VM from a JSON configuration") 1100 .arg(Arg::new("path").index(1).default_value("-")), 1101 ) 1102 .subcommand(Command::new("ping").about("Ping the VMM to check for API server availability")) 1103 .subcommand(Command::new("shutdown-vmm").about("Shutdown the VMM")) 1104 .subcommand(Command::new("nmi").about("Trigger NMI")); 1105 1106 let matches = app.get_matches(); 1107 1108 let mut target_api = match ( 1109 matches.get_one::<String>("api-socket"), 1110 #[cfg(feature = "dbus_api")] 1111 matches.get_one::<String>("dbus-service-name"), 1112 #[cfg(feature = "dbus_api")] 1113 matches.get_one::<String>("dbus-object-path"), 1114 ) { 1115 #[cfg(not(feature = "dbus_api"))] 1116 (Some(api_sock),) => TargetApi::HttpApi( 1117 UnixStream::connect(api_sock).unwrap_or_else(|e| { 1118 eprintln!("Error opening HTTP socket: {e}"); 1119 process::exit(1) 1120 }), 1121 PhantomData, 1122 ), 1123 #[cfg(feature = "dbus_api")] 1124 (Some(api_sock), None, None) => TargetApi::HttpApi( 1125 UnixStream::connect(api_sock).unwrap_or_else(|e| { 1126 eprintln!("Error opening HTTP socket: {e}"); 1127 process::exit(1) 1128 }), 1129 PhantomData, 1130 ), 1131 #[cfg(feature = "dbus_api")] 1132 (None, Some(dbus_name), Some(dbus_path)) => TargetApi::DBusApi( 1133 DBusApi1ProxyBlocking::new_connection( 1134 dbus_name, 1135 dbus_path, 1136 matches.get_flag("dbus-system-bus"), 1137 ) 1138 .map_err(Error::DBusApiClient) 1139 .unwrap_or_else(|e| { 1140 eprintln!("Error creating D-Bus proxy: {e}"); 1141 process::exit(1) 1142 }), 1143 ), 1144 #[cfg(feature = "dbus_api")] 1145 (Some(_), Some(_) | None, Some(_) | None) => { 1146 println!( 1147 "`api-socket` and (dbus-service-name or dbus-object-path) are mutually exclusive" 1148 ); 1149 process::exit(1); 1150 } 1151 _ => { 1152 println!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); 1153 process::exit(1); 1154 } 1155 }; 1156 1157 if let Err(e) = target_api.do_command(&matches) { 1158 eprintln!("Error running command: {e}"); 1159 process::exit(1) 1160 }; 1161 } 1162