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