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::{dbus_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 #[dbus_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 = restore_config( 449 matches 450 .subcommand_matches("restore") 451 .unwrap() 452 .get_one::<String>("restore_config") 453 .unwrap(), 454 )?; 455 simple_api_command(socket, "PUT", "restore", Some(&restore_config)) 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 = 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, Error> { 853 let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?; 854 let restore_config = serde_json::to_string(&restore_config).unwrap(); 855 856 Ok(restore_config) 857 } 858 859 fn coredump_config(destination_url: &str) -> String { 860 let coredump_config = vmm::api::VmCoredumpData { 861 destination_url: String::from(destination_url), 862 }; 863 864 serde_json::to_string(&coredump_config).unwrap() 865 } 866 867 fn receive_migration_data(url: &str) -> String { 868 let receive_migration_data = vmm::api::VmReceiveMigrationData { 869 receiver_url: url.to_owned(), 870 }; 871 872 serde_json::to_string(&receive_migration_data).unwrap() 873 } 874 875 fn send_migration_data(url: &str, local: bool) -> String { 876 let send_migration_data = vmm::api::VmSendMigrationData { 877 destination_url: url.to_owned(), 878 local, 879 }; 880 881 serde_json::to_string(&send_migration_data).unwrap() 882 } 883 884 fn create_data(path: &str) -> Result<String, Error> { 885 let mut data = String::default(); 886 if path == "-" { 887 std::io::stdin() 888 .read_to_string(&mut data) 889 .map_err(Error::ReadingStdin)?; 890 } else { 891 data = std::fs::read_to_string(path).map_err(Error::ReadingFile)?; 892 } 893 894 Ok(data) 895 } 896 897 fn main() { 898 let app = Command::new("ch-remote") 899 .author(env!("CARGO_PKG_AUTHORS")) 900 .version(env!("BUILD_VERSION")) 901 .about("Remotely control a cloud-hypervisor VMM.") 902 .arg_required_else_help(true) 903 .subcommand_required(true) 904 .args([ 905 Arg::new("api-socket") 906 .long("api-socket") 907 .help("HTTP API socket path (UNIX domain socket).") 908 .num_args(1), 909 #[cfg(feature = "dbus_api")] 910 Arg::new("dbus-service-name") 911 .long("dbus-service-name") 912 .help("Well known name of the dbus service") 913 .num_args(1), 914 #[cfg(feature = "dbus_api")] 915 Arg::new("dbus-object-path") 916 .long("dbus-object-path") 917 .help("Object path which the interface is being served at") 918 .num_args(1), 919 #[cfg(feature = "dbus_api")] 920 Arg::new("dbus-system-bus") 921 .long("dbus-system-bus") 922 .action(ArgAction::SetTrue) 923 .num_args(0) 924 .help("Use the system bus instead of a session bus"), 925 ]) 926 .subcommand( 927 Command::new("add-device").about("Add VFIO device").arg( 928 Arg::new("device_config") 929 .index(1) 930 .help(vmm::config::DeviceConfig::SYNTAX), 931 ), 932 ) 933 .subcommand( 934 Command::new("add-disk").about("Add block device").arg( 935 Arg::new("disk_config") 936 .index(1) 937 .help(vmm::config::DiskConfig::SYNTAX), 938 ), 939 ) 940 .subcommand( 941 Command::new("add-fs") 942 .about("Add virtio-fs backed fs device") 943 .arg( 944 Arg::new("fs_config") 945 .index(1) 946 .help(vmm::config::FsConfig::SYNTAX), 947 ), 948 ) 949 .subcommand( 950 Command::new("add-pmem") 951 .about("Add persistent memory device") 952 .arg( 953 Arg::new("pmem_config") 954 .index(1) 955 .help(vmm::config::PmemConfig::SYNTAX), 956 ), 957 ) 958 .subcommand( 959 Command::new("add-net").about("Add network device").arg( 960 Arg::new("net_config") 961 .index(1) 962 .help(vmm::config::NetConfig::SYNTAX), 963 ), 964 ) 965 .subcommand( 966 Command::new("add-user-device") 967 .about("Add userspace device") 968 .arg( 969 Arg::new("device_config") 970 .index(1) 971 .help(vmm::config::UserDeviceConfig::SYNTAX), 972 ), 973 ) 974 .subcommand( 975 Command::new("add-vdpa").about("Add vDPA device").arg( 976 Arg::new("vdpa_config") 977 .index(1) 978 .help(vmm::config::VdpaConfig::SYNTAX), 979 ), 980 ) 981 .subcommand( 982 Command::new("add-vsock").about("Add vsock device").arg( 983 Arg::new("vsock_config") 984 .index(1) 985 .help(vmm::config::VsockConfig::SYNTAX), 986 ), 987 ) 988 .subcommand( 989 Command::new("remove-device") 990 .about("Remove VFIO device") 991 .arg(Arg::new("id").index(1).help("<device_id>")), 992 ) 993 .subcommand(Command::new("info").about("Info on the VM")) 994 .subcommand(Command::new("counters").about("Counters from the VM")) 995 .subcommand(Command::new("pause").about("Pause the VM")) 996 .subcommand(Command::new("reboot").about("Reboot the VM")) 997 .subcommand(Command::new("power-button").about("Trigger a power button in the VM")) 998 .subcommand( 999 Command::new("resize") 1000 .about("Resize the VM") 1001 .arg( 1002 Arg::new("cpus") 1003 .long("cpus") 1004 .help("New vCPUs count") 1005 .num_args(1), 1006 ) 1007 .arg( 1008 Arg::new("memory") 1009 .long("memory") 1010 .help("New memory size in bytes (supports K/M/G suffix)") 1011 .num_args(1), 1012 ) 1013 .arg( 1014 Arg::new("balloon") 1015 .long("balloon") 1016 .help("New balloon size in bytes (supports K/M/G suffix)") 1017 .num_args(1), 1018 ), 1019 ) 1020 .subcommand( 1021 Command::new("resize-zone") 1022 .about("Resize a memory zone") 1023 .arg( 1024 Arg::new("id") 1025 .long("id") 1026 .help("Memory zone identifier") 1027 .num_args(1), 1028 ) 1029 .arg( 1030 Arg::new("size") 1031 .long("size") 1032 .help("New memory zone size in bytes (supports K/M/G suffix)") 1033 .num_args(1), 1034 ), 1035 ) 1036 .subcommand(Command::new("resume").about("Resume the VM")) 1037 .subcommand(Command::new("boot").about("Boot a created VM")) 1038 .subcommand(Command::new("delete").about("Delete a VM")) 1039 .subcommand(Command::new("shutdown").about("Shutdown the VM")) 1040 .subcommand( 1041 Command::new("snapshot") 1042 .about("Create a snapshot from VM") 1043 .arg( 1044 Arg::new("snapshot_config") 1045 .index(1) 1046 .help("<destination_url>"), 1047 ), 1048 ) 1049 .subcommand( 1050 Command::new("restore") 1051 .about("Restore VM from a snapshot") 1052 .arg( 1053 Arg::new("restore_config") 1054 .index(1) 1055 .help(vmm::config::RestoreConfig::SYNTAX), 1056 ), 1057 ) 1058 .subcommand( 1059 Command::new("coredump") 1060 .about("Create a coredump from VM") 1061 .arg(Arg::new("coredump_config").index(1).help("<file_path>")), 1062 ) 1063 .subcommand( 1064 Command::new("send-migration") 1065 .about("Initiate a VM migration") 1066 .arg( 1067 Arg::new("send_migration_config") 1068 .index(1) 1069 .help("<destination_url>"), 1070 ) 1071 .arg( 1072 Arg::new("send_migration_local") 1073 .long("local") 1074 .num_args(0) 1075 .action(ArgAction::SetTrue), 1076 ), 1077 ) 1078 .subcommand( 1079 Command::new("receive-migration") 1080 .about("Receive a VM migration") 1081 .arg( 1082 Arg::new("receive_migration_config") 1083 .index(1) 1084 .help("<receiver_url>"), 1085 ), 1086 ) 1087 .subcommand( 1088 Command::new("create") 1089 .about("Create VM from a JSON configuration") 1090 .arg(Arg::new("path").index(1).default_value("-")), 1091 ) 1092 .subcommand(Command::new("ping").about("Ping the VMM to check for API server availability")) 1093 .subcommand(Command::new("shutdown-vmm").about("Shutdown the VMM")) 1094 .subcommand(Command::new("nmi").about("Trigger NMI")); 1095 1096 let matches = app.get_matches(); 1097 1098 let mut target_api = match ( 1099 matches.get_one::<String>("api-socket"), 1100 #[cfg(feature = "dbus_api")] 1101 matches.get_one::<String>("dbus-service-name"), 1102 #[cfg(feature = "dbus_api")] 1103 matches.get_one::<String>("dbus-object-path"), 1104 ) { 1105 #[cfg(not(feature = "dbus_api"))] 1106 (Some(api_sock),) => TargetApi::HttpApi( 1107 UnixStream::connect(api_sock).unwrap_or_else(|e| { 1108 eprintln!("Error opening HTTP socket: {e}"); 1109 process::exit(1) 1110 }), 1111 PhantomData, 1112 ), 1113 #[cfg(feature = "dbus_api")] 1114 (Some(api_sock), None, None) => TargetApi::HttpApi( 1115 UnixStream::connect(api_sock).unwrap_or_else(|e| { 1116 eprintln!("Error opening HTTP socket: {e}"); 1117 process::exit(1) 1118 }), 1119 PhantomData, 1120 ), 1121 #[cfg(feature = "dbus_api")] 1122 (None, Some(dbus_name), Some(dbus_path)) => TargetApi::DBusApi( 1123 DBusApi1ProxyBlocking::new_connection( 1124 dbus_name, 1125 dbus_path, 1126 matches.get_flag("dbus-system-bus"), 1127 ) 1128 .map_err(Error::DBusApiClient) 1129 .unwrap_or_else(|e| { 1130 eprintln!("Error creating D-Bus proxy: {e}"); 1131 process::exit(1) 1132 }), 1133 ), 1134 #[cfg(feature = "dbus_api")] 1135 (Some(_), Some(_) | None, Some(_) | None) => { 1136 println!( 1137 "`api-socket` and (dbus-service-name or dbus-object-path) are mutually exclusive" 1138 ); 1139 process::exit(1); 1140 } 1141 _ => { 1142 println!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); 1143 process::exit(1); 1144 } 1145 }; 1146 1147 if let Err(e) = target_api.do_command(&matches) { 1148 eprintln!("Error running command: {e}"); 1149 process::exit(1) 1150 }; 1151 } 1152