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("resize") => { 301 let resize = resize_config( 302 matches 303 .subcommand_matches("resize") 304 .unwrap() 305 .get_one::<String>("cpus") 306 .map(|x| x as &str), 307 matches 308 .subcommand_matches("resize") 309 .unwrap() 310 .get_one::<String>("memory") 311 .map(|x| x as &str), 312 matches 313 .subcommand_matches("resize") 314 .unwrap() 315 .get_one::<String>("balloon") 316 .map(|x| x as &str), 317 )?; 318 simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient) 319 } 320 Some("resize-zone") => { 321 let resize_zone = resize_zone_config( 322 matches 323 .subcommand_matches("resize-zone") 324 .unwrap() 325 .get_one::<String>("id") 326 .unwrap(), 327 matches 328 .subcommand_matches("resize-zone") 329 .unwrap() 330 .get_one::<String>("size") 331 .unwrap(), 332 )?; 333 simple_api_command(socket, "PUT", "resize-zone", Some(&resize_zone)) 334 .map_err(Error::HttpApiClient) 335 } 336 Some("add-device") => { 337 let device_config = add_device_config( 338 matches 339 .subcommand_matches("add-device") 340 .unwrap() 341 .get_one::<String>("device_config") 342 .unwrap(), 343 )?; 344 simple_api_command(socket, "PUT", "add-device", Some(&device_config)) 345 .map_err(Error::HttpApiClient) 346 } 347 Some("remove-device") => { 348 let remove_device_data = remove_device_config( 349 matches 350 .subcommand_matches("remove-device") 351 .unwrap() 352 .get_one::<String>("id") 353 .unwrap(), 354 ); 355 simple_api_command(socket, "PUT", "remove-device", Some(&remove_device_data)) 356 .map_err(Error::HttpApiClient) 357 } 358 Some("add-disk") => { 359 let disk_config = add_disk_config( 360 matches 361 .subcommand_matches("add-disk") 362 .unwrap() 363 .get_one::<String>("disk_config") 364 .unwrap(), 365 )?; 366 simple_api_command(socket, "PUT", "add-disk", Some(&disk_config)) 367 .map_err(Error::HttpApiClient) 368 } 369 Some("add-fs") => { 370 let fs_config = add_fs_config( 371 matches 372 .subcommand_matches("add-fs") 373 .unwrap() 374 .get_one::<String>("fs_config") 375 .unwrap(), 376 )?; 377 simple_api_command(socket, "PUT", "add-fs", Some(&fs_config)) 378 .map_err(Error::HttpApiClient) 379 } 380 Some("add-pmem") => { 381 let pmem_config = add_pmem_config( 382 matches 383 .subcommand_matches("add-pmem") 384 .unwrap() 385 .get_one::<String>("pmem_config") 386 .unwrap(), 387 )?; 388 simple_api_command(socket, "PUT", "add-pmem", Some(&pmem_config)) 389 .map_err(Error::HttpApiClient) 390 } 391 Some("add-net") => { 392 let (net_config, fds) = add_net_config( 393 matches 394 .subcommand_matches("add-net") 395 .unwrap() 396 .get_one::<String>("net_config") 397 .unwrap(), 398 )?; 399 simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), fds) 400 .map_err(Error::HttpApiClient) 401 } 402 Some("add-user-device") => { 403 let device_config = add_user_device_config( 404 matches 405 .subcommand_matches("add-user-device") 406 .unwrap() 407 .get_one::<String>("device_config") 408 .unwrap(), 409 )?; 410 simple_api_command(socket, "PUT", "add-user-device", Some(&device_config)) 411 .map_err(Error::HttpApiClient) 412 } 413 Some("add-vdpa") => { 414 let vdpa_config = add_vdpa_config( 415 matches 416 .subcommand_matches("add-vdpa") 417 .unwrap() 418 .get_one::<String>("vdpa_config") 419 .unwrap(), 420 )?; 421 simple_api_command(socket, "PUT", "add-vdpa", Some(&vdpa_config)) 422 .map_err(Error::HttpApiClient) 423 } 424 Some("add-vsock") => { 425 let vsock_config = add_vsock_config( 426 matches 427 .subcommand_matches("add-vsock") 428 .unwrap() 429 .get_one::<String>("vsock_config") 430 .unwrap(), 431 )?; 432 simple_api_command(socket, "PUT", "add-vsock", Some(&vsock_config)) 433 .map_err(Error::HttpApiClient) 434 } 435 Some("snapshot") => { 436 let snapshot_config = snapshot_config( 437 matches 438 .subcommand_matches("snapshot") 439 .unwrap() 440 .get_one::<String>("snapshot_config") 441 .unwrap(), 442 ); 443 simple_api_command(socket, "PUT", "snapshot", Some(&snapshot_config)) 444 .map_err(Error::HttpApiClient) 445 } 446 Some("restore") => { 447 let restore_config = restore_config( 448 matches 449 .subcommand_matches("restore") 450 .unwrap() 451 .get_one::<String>("restore_config") 452 .unwrap(), 453 )?; 454 simple_api_command(socket, "PUT", "restore", Some(&restore_config)) 455 .map_err(Error::HttpApiClient) 456 } 457 Some("coredump") => { 458 let coredump_config = coredump_config( 459 matches 460 .subcommand_matches("coredump") 461 .unwrap() 462 .get_one::<String>("coredump_config") 463 .unwrap(), 464 ); 465 simple_api_command(socket, "PUT", "coredump", Some(&coredump_config)) 466 .map_err(Error::HttpApiClient) 467 } 468 Some("send-migration") => { 469 let send_migration_data = send_migration_data( 470 matches 471 .subcommand_matches("send-migration") 472 .unwrap() 473 .get_one::<String>("send_migration_config") 474 .unwrap(), 475 matches 476 .subcommand_matches("send-migration") 477 .unwrap() 478 .get_flag("send_migration_local"), 479 ); 480 simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data)) 481 .map_err(Error::HttpApiClient) 482 } 483 Some("receive-migration") => { 484 let receive_migration_data = receive_migration_data( 485 matches 486 .subcommand_matches("receive-migration") 487 .unwrap() 488 .get_one::<String>("receive_migration_config") 489 .unwrap(), 490 ); 491 simple_api_command( 492 socket, 493 "PUT", 494 "receive-migration", 495 Some(&receive_migration_data), 496 ) 497 .map_err(Error::HttpApiClient) 498 } 499 Some("create") => { 500 let data = create_data( 501 matches 502 .subcommand_matches("create") 503 .unwrap() 504 .get_one::<String>("path") 505 .unwrap(), 506 )?; 507 simple_api_command(socket, "PUT", "create", Some(&data)).map_err(Error::HttpApiClient) 508 } 509 _ => unreachable!(), 510 } 511 } 512 513 #[cfg(feature = "dbus_api")] 514 fn dbus_api_do_command(matches: &ArgMatches, proxy: &DBusApi1ProxyBlocking<'_>) -> ApiResult { 515 match matches.subcommand_name() { 516 Some("boot") => proxy.api_vm_boot(), 517 Some("delete") => proxy.api_vm_delete(), 518 Some("shutdown-vmm") => proxy.api_vmm_shutdown(), 519 Some("resume") => proxy.api_vm_resume(), 520 Some("power-button") => proxy.api_vm_power_button(), 521 Some("reboot") => proxy.api_vm_reboot(), 522 Some("pause") => proxy.api_vm_pause(), 523 Some("info") => proxy.api_vm_info(), 524 Some("counters") => proxy.api_vm_counters(), 525 Some("ping") => proxy.api_vmm_ping(), 526 Some("shutdown") => proxy.api_vm_shutdown(), 527 Some("resize") => { 528 let resize = resize_config( 529 matches 530 .subcommand_matches("resize") 531 .unwrap() 532 .get_one::<String>("cpus") 533 .map(|x| x as &str), 534 matches 535 .subcommand_matches("resize") 536 .unwrap() 537 .get_one::<String>("memory") 538 .map(|x| x as &str), 539 matches 540 .subcommand_matches("resize") 541 .unwrap() 542 .get_one::<String>("balloon") 543 .map(|x| x as &str), 544 )?; 545 proxy.api_vm_resize(&resize) 546 } 547 Some("resize-zone") => { 548 let resize_zone = resize_zone_config( 549 matches 550 .subcommand_matches("resize-zone") 551 .unwrap() 552 .get_one::<String>("id") 553 .unwrap(), 554 matches 555 .subcommand_matches("resize-zone") 556 .unwrap() 557 .get_one::<String>("size") 558 .unwrap(), 559 )?; 560 proxy.api_vm_resize_zone(&resize_zone) 561 } 562 Some("add-device") => { 563 let device_config = add_device_config( 564 matches 565 .subcommand_matches("add-device") 566 .unwrap() 567 .get_one::<String>("device_config") 568 .unwrap(), 569 )?; 570 proxy.api_vm_add_device(&device_config) 571 } 572 Some("remove-device") => { 573 let remove_device_data = remove_device_config( 574 matches 575 .subcommand_matches("remove-device") 576 .unwrap() 577 .get_one::<String>("id") 578 .unwrap(), 579 ); 580 proxy.api_vm_remove_device(&remove_device_data) 581 } 582 Some("add-disk") => { 583 let disk_config = add_disk_config( 584 matches 585 .subcommand_matches("add-disk") 586 .unwrap() 587 .get_one::<String>("disk_config") 588 .unwrap(), 589 )?; 590 proxy.api_vm_add_disk(&disk_config) 591 } 592 Some("add-fs") => { 593 let fs_config = add_fs_config( 594 matches 595 .subcommand_matches("add-fs") 596 .unwrap() 597 .get_one::<String>("fs_config") 598 .unwrap(), 599 )?; 600 proxy.api_vm_add_fs(&fs_config) 601 } 602 Some("add-pmem") => { 603 let pmem_config = add_pmem_config( 604 matches 605 .subcommand_matches("add-pmem") 606 .unwrap() 607 .get_one::<String>("pmem_config") 608 .unwrap(), 609 )?; 610 proxy.api_vm_add_pmem(&pmem_config) 611 } 612 Some("add-net") => { 613 let (net_config, _fds) = add_net_config( 614 matches 615 .subcommand_matches("add-net") 616 .unwrap() 617 .get_one::<String>("net_config") 618 .unwrap(), 619 )?; 620 proxy.api_vm_add_net(&net_config) 621 } 622 Some("add-user-device") => { 623 let device_config = add_user_device_config( 624 matches 625 .subcommand_matches("add-user-device") 626 .unwrap() 627 .get_one::<String>("device_config") 628 .unwrap(), 629 )?; 630 proxy.api_vm_add_user_device(&device_config) 631 } 632 Some("add-vdpa") => { 633 let vdpa_config = add_vdpa_config( 634 matches 635 .subcommand_matches("add-vdpa") 636 .unwrap() 637 .get_one::<String>("vdpa_config") 638 .unwrap(), 639 )?; 640 proxy.api_vm_add_vdpa(&vdpa_config) 641 } 642 Some("add-vsock") => { 643 let vsock_config = add_vsock_config( 644 matches 645 .subcommand_matches("add-vsock") 646 .unwrap() 647 .get_one::<String>("vsock_config") 648 .unwrap(), 649 )?; 650 proxy.api_vm_add_vsock(&vsock_config) 651 } 652 Some("snapshot") => { 653 let snapshot_config = snapshot_config( 654 matches 655 .subcommand_matches("snapshot") 656 .unwrap() 657 .get_one::<String>("snapshot_config") 658 .unwrap(), 659 ); 660 proxy.api_vm_snapshot(&snapshot_config) 661 } 662 Some("restore") => { 663 let restore_config = restore_config( 664 matches 665 .subcommand_matches("restore") 666 .unwrap() 667 .get_one::<String>("restore_config") 668 .unwrap(), 669 )?; 670 proxy.api_vm_restore(&restore_config) 671 } 672 Some("coredump") => { 673 let coredump_config = coredump_config( 674 matches 675 .subcommand_matches("coredump") 676 .unwrap() 677 .get_one::<String>("coredump_config") 678 .unwrap(), 679 ); 680 proxy.api_vm_coredump(&coredump_config) 681 } 682 Some("send-migration") => { 683 let send_migration_data = send_migration_data( 684 matches 685 .subcommand_matches("send-migration") 686 .unwrap() 687 .get_one::<String>("send_migration_config") 688 .unwrap(), 689 matches 690 .subcommand_matches("send-migration") 691 .unwrap() 692 .get_flag("send_migration_local"), 693 ); 694 proxy.api_vm_send_migration(&send_migration_data) 695 } 696 Some("receive-migration") => { 697 let receive_migration_data = receive_migration_data( 698 matches 699 .subcommand_matches("receive-migration") 700 .unwrap() 701 .get_one::<String>("receive_migration_config") 702 .unwrap(), 703 ); 704 proxy.api_vm_receive_migration(&receive_migration_data) 705 } 706 Some("create") => { 707 let data = create_data( 708 matches 709 .subcommand_matches("create") 710 .unwrap() 711 .get_one::<String>("path") 712 .unwrap(), 713 )?; 714 proxy.api_vm_create(&data) 715 } 716 _ => unreachable!(), 717 } 718 } 719 720 fn resize_config( 721 cpus: Option<&str>, 722 memory: Option<&str>, 723 balloon: Option<&str>, 724 ) -> Result<String, Error> { 725 let desired_vcpus: Option<u8> = if let Some(cpus) = cpus { 726 Some(cpus.parse().map_err(Error::InvalidCpuCount)?) 727 } else { 728 None 729 }; 730 731 let desired_ram: Option<u64> = if let Some(memory) = memory { 732 Some( 733 memory 734 .parse::<ByteSized>() 735 .map_err(Error::InvalidMemorySize)? 736 .0, 737 ) 738 } else { 739 None 740 }; 741 742 let desired_balloon: Option<u64> = if let Some(balloon) = balloon { 743 Some( 744 balloon 745 .parse::<ByteSized>() 746 .map_err(Error::InvalidBalloonSize)? 747 .0, 748 ) 749 } else { 750 None 751 }; 752 753 let resize = vmm::api::VmResizeData { 754 desired_vcpus, 755 desired_ram, 756 desired_balloon, 757 }; 758 759 Ok(serde_json::to_string(&resize).unwrap()) 760 } 761 762 fn resize_zone_config(id: &str, size: &str) -> Result<String, Error> { 763 let resize_zone = vmm::api::VmResizeZoneData { 764 id: id.to_owned(), 765 desired_ram: size 766 .parse::<ByteSized>() 767 .map_err(Error::InvalidMemorySize)? 768 .0, 769 }; 770 771 Ok(serde_json::to_string(&resize_zone).unwrap()) 772 } 773 774 fn add_device_config(config: &str) -> Result<String, Error> { 775 let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?; 776 let device_config = serde_json::to_string(&device_config).unwrap(); 777 778 Ok(device_config) 779 } 780 781 fn add_user_device_config(config: &str) -> Result<String, Error> { 782 let device_config = 783 vmm::config::UserDeviceConfig::parse(config).map_err(Error::AddUserDeviceConfig)?; 784 let device_config = serde_json::to_string(&device_config).unwrap(); 785 786 Ok(device_config) 787 } 788 789 fn remove_device_config(id: &str) -> String { 790 let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() }; 791 792 serde_json::to_string(&remove_device_data).unwrap() 793 } 794 795 fn add_disk_config(config: &str) -> Result<String, Error> { 796 let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?; 797 let disk_config = serde_json::to_string(&disk_config).unwrap(); 798 799 Ok(disk_config) 800 } 801 802 fn add_fs_config(config: &str) -> Result<String, Error> { 803 let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?; 804 let fs_config = serde_json::to_string(&fs_config).unwrap(); 805 806 Ok(fs_config) 807 } 808 809 fn add_pmem_config(config: &str) -> Result<String, Error> { 810 let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?; 811 let pmem_config = serde_json::to_string(&pmem_config).unwrap(); 812 813 Ok(pmem_config) 814 } 815 816 fn add_net_config(config: &str) -> Result<(String, Vec<i32>), Error> { 817 let mut net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?; 818 819 // NetConfig is modified on purpose here by taking the list of file 820 // descriptors out. Keeping the list and send it to the server side 821 // process would not make any sense since the file descriptor may be 822 // represented with different values. 823 let fds = net_config.fds.take().unwrap_or_default(); 824 let net_config = serde_json::to_string(&net_config).unwrap(); 825 826 Ok((net_config, fds)) 827 } 828 829 fn add_vdpa_config(config: &str) -> Result<String, Error> { 830 let vdpa_config = vmm::config::VdpaConfig::parse(config).map_err(Error::AddVdpaConfig)?; 831 let vdpa_config = serde_json::to_string(&vdpa_config).unwrap(); 832 833 Ok(vdpa_config) 834 } 835 836 fn add_vsock_config(config: &str) -> Result<String, Error> { 837 let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?; 838 let vsock_config = serde_json::to_string(&vsock_config).unwrap(); 839 840 Ok(vsock_config) 841 } 842 843 fn snapshot_config(url: &str) -> String { 844 let snapshot_config = vmm::api::VmSnapshotConfig { 845 destination_url: String::from(url), 846 }; 847 848 serde_json::to_string(&snapshot_config).unwrap() 849 } 850 851 fn restore_config(config: &str) -> Result<String, Error> { 852 let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?; 853 let restore_config = serde_json::to_string(&restore_config).unwrap(); 854 855 Ok(restore_config) 856 } 857 858 fn coredump_config(destination_url: &str) -> String { 859 let coredump_config = vmm::api::VmCoredumpData { 860 destination_url: String::from(destination_url), 861 }; 862 863 serde_json::to_string(&coredump_config).unwrap() 864 } 865 866 fn receive_migration_data(url: &str) -> String { 867 let receive_migration_data = vmm::api::VmReceiveMigrationData { 868 receiver_url: url.to_owned(), 869 }; 870 871 serde_json::to_string(&receive_migration_data).unwrap() 872 } 873 874 fn send_migration_data(url: &str, local: bool) -> String { 875 let send_migration_data = vmm::api::VmSendMigrationData { 876 destination_url: url.to_owned(), 877 local, 878 }; 879 880 serde_json::to_string(&send_migration_data).unwrap() 881 } 882 883 fn create_data(path: &str) -> Result<String, Error> { 884 let mut data = String::default(); 885 if path == "-" { 886 std::io::stdin() 887 .read_to_string(&mut data) 888 .map_err(Error::ReadingStdin)?; 889 } else { 890 data = std::fs::read_to_string(path).map_err(Error::ReadingFile)?; 891 } 892 893 Ok(data) 894 } 895 896 fn main() { 897 let app = Command::new("ch-remote") 898 .author(env!("CARGO_PKG_AUTHORS")) 899 .version(env!("BUILD_VERSION")) 900 .about("Remotely control a cloud-hypervisor VMM.") 901 .arg_required_else_help(true) 902 .subcommand_required(true) 903 .args([ 904 Arg::new("api-socket") 905 .long("api-socket") 906 .help("HTTP API socket path (UNIX domain socket).") 907 .num_args(1), 908 #[cfg(feature = "dbus_api")] 909 Arg::new("dbus-service-name") 910 .long("dbus-service-name") 911 .help("Well known name of the dbus service") 912 .num_args(1), 913 #[cfg(feature = "dbus_api")] 914 Arg::new("dbus-object-path") 915 .long("dbus-object-path") 916 .help("Object path which the interface is being served at") 917 .num_args(1), 918 #[cfg(feature = "dbus_api")] 919 Arg::new("dbus-system-bus") 920 .long("dbus-system-bus") 921 .action(ArgAction::SetTrue) 922 .num_args(0) 923 .help("Use the system bus instead of a session bus"), 924 ]) 925 .subcommand( 926 Command::new("add-device").about("Add VFIO device").arg( 927 Arg::new("device_config") 928 .index(1) 929 .help(vmm::config::DeviceConfig::SYNTAX), 930 ), 931 ) 932 .subcommand( 933 Command::new("add-disk").about("Add block device").arg( 934 Arg::new("disk_config") 935 .index(1) 936 .help(vmm::config::DiskConfig::SYNTAX), 937 ), 938 ) 939 .subcommand( 940 Command::new("add-fs") 941 .about("Add virtio-fs backed fs device") 942 .arg( 943 Arg::new("fs_config") 944 .index(1) 945 .help(vmm::config::FsConfig::SYNTAX), 946 ), 947 ) 948 .subcommand( 949 Command::new("add-pmem") 950 .about("Add persistent memory device") 951 .arg( 952 Arg::new("pmem_config") 953 .index(1) 954 .help(vmm::config::PmemConfig::SYNTAX), 955 ), 956 ) 957 .subcommand( 958 Command::new("add-net").about("Add network device").arg( 959 Arg::new("net_config") 960 .index(1) 961 .help(vmm::config::NetConfig::SYNTAX), 962 ), 963 ) 964 .subcommand( 965 Command::new("add-user-device") 966 .about("Add userspace device") 967 .arg( 968 Arg::new("device_config") 969 .index(1) 970 .help(vmm::config::UserDeviceConfig::SYNTAX), 971 ), 972 ) 973 .subcommand( 974 Command::new("add-vdpa").about("Add vDPA device").arg( 975 Arg::new("vdpa_config") 976 .index(1) 977 .help(vmm::config::VdpaConfig::SYNTAX), 978 ), 979 ) 980 .subcommand( 981 Command::new("add-vsock").about("Add vsock device").arg( 982 Arg::new("vsock_config") 983 .index(1) 984 .help(vmm::config::VsockConfig::SYNTAX), 985 ), 986 ) 987 .subcommand( 988 Command::new("remove-device") 989 .about("Remove VFIO device") 990 .arg(Arg::new("id").index(1).help("<device_id>")), 991 ) 992 .subcommand(Command::new("info").about("Info on the VM")) 993 .subcommand(Command::new("counters").about("Counters from the VM")) 994 .subcommand(Command::new("pause").about("Pause the VM")) 995 .subcommand(Command::new("reboot").about("Reboot the VM")) 996 .subcommand(Command::new("power-button").about("Trigger a power button in the VM")) 997 .subcommand( 998 Command::new("resize") 999 .about("Resize the VM") 1000 .arg( 1001 Arg::new("cpus") 1002 .long("cpus") 1003 .help("New vCPUs count") 1004 .num_args(1), 1005 ) 1006 .arg( 1007 Arg::new("memory") 1008 .long("memory") 1009 .help("New memory size in bytes (supports K/M/G suffix)") 1010 .num_args(1), 1011 ) 1012 .arg( 1013 Arg::new("balloon") 1014 .long("balloon") 1015 .help("New balloon size in bytes (supports K/M/G suffix)") 1016 .num_args(1), 1017 ), 1018 ) 1019 .subcommand( 1020 Command::new("resize-zone") 1021 .about("Resize a memory zone") 1022 .arg( 1023 Arg::new("id") 1024 .long("id") 1025 .help("Memory zone identifier") 1026 .num_args(1), 1027 ) 1028 .arg( 1029 Arg::new("size") 1030 .long("size") 1031 .help("New memory zone size in bytes (supports K/M/G suffix)") 1032 .num_args(1), 1033 ), 1034 ) 1035 .subcommand(Command::new("resume").about("Resume the VM")) 1036 .subcommand(Command::new("boot").about("Boot a created VM")) 1037 .subcommand(Command::new("delete").about("Delete a VM")) 1038 .subcommand(Command::new("shutdown").about("Shutdown the VM")) 1039 .subcommand( 1040 Command::new("snapshot") 1041 .about("Create a snapshot from VM") 1042 .arg( 1043 Arg::new("snapshot_config") 1044 .index(1) 1045 .help("<destination_url>"), 1046 ), 1047 ) 1048 .subcommand( 1049 Command::new("restore") 1050 .about("Restore VM from a snapshot") 1051 .arg( 1052 Arg::new("restore_config") 1053 .index(1) 1054 .help(vmm::config::RestoreConfig::SYNTAX), 1055 ), 1056 ) 1057 .subcommand( 1058 Command::new("coredump") 1059 .about("Create a coredump from VM") 1060 .arg(Arg::new("coredump_config").index(1).help("<file_path>")), 1061 ) 1062 .subcommand( 1063 Command::new("send-migration") 1064 .about("Initiate a VM migration") 1065 .arg( 1066 Arg::new("send_migration_config") 1067 .index(1) 1068 .help("<destination_url>"), 1069 ) 1070 .arg( 1071 Arg::new("send_migration_local") 1072 .long("local") 1073 .num_args(0) 1074 .action(ArgAction::SetTrue), 1075 ), 1076 ) 1077 .subcommand( 1078 Command::new("receive-migration") 1079 .about("Receive a VM migration") 1080 .arg( 1081 Arg::new("receive_migration_config") 1082 .index(1) 1083 .help("<receiver_url>"), 1084 ), 1085 ) 1086 .subcommand( 1087 Command::new("create") 1088 .about("Create VM from a JSON configuration") 1089 .arg(Arg::new("path").index(1).default_value("-")), 1090 ) 1091 .subcommand(Command::new("ping").about("Ping the VMM to check for API server availability")) 1092 .subcommand(Command::new("shutdown-vmm").about("Shutdown the VMM")); 1093 1094 let matches = app.get_matches(); 1095 1096 let mut target_api = match ( 1097 matches.get_one::<String>("api-socket"), 1098 #[cfg(feature = "dbus_api")] 1099 matches.get_one::<String>("dbus-service-name"), 1100 #[cfg(feature = "dbus_api")] 1101 matches.get_one::<String>("dbus-object-path"), 1102 ) { 1103 #[cfg(not(feature = "dbus_api"))] 1104 (Some(api_sock),) => TargetApi::HttpApi( 1105 UnixStream::connect(api_sock).unwrap_or_else(|e| { 1106 eprintln!("Error opening HTTP socket: {e}"); 1107 process::exit(1) 1108 }), 1109 PhantomData, 1110 ), 1111 #[cfg(feature = "dbus_api")] 1112 (Some(api_sock), None, None) => TargetApi::HttpApi( 1113 UnixStream::connect(api_sock).unwrap_or_else(|e| { 1114 eprintln!("Error opening HTTP socket: {e}"); 1115 process::exit(1) 1116 }), 1117 PhantomData, 1118 ), 1119 #[cfg(feature = "dbus_api")] 1120 (None, Some(dbus_name), Some(dbus_path)) => TargetApi::DBusApi( 1121 DBusApi1ProxyBlocking::new_connection( 1122 dbus_name, 1123 dbus_path, 1124 matches.get_flag("dbus-system-bus"), 1125 ) 1126 .map_err(Error::DBusApiClient) 1127 .unwrap_or_else(|e| { 1128 eprintln!("Error creating D-Bus proxy: {e}"); 1129 process::exit(1) 1130 }), 1131 ), 1132 #[cfg(feature = "dbus_api")] 1133 (Some(_), Some(_) | None, Some(_) | None) => { 1134 println!( 1135 "`api-socket` and (dbus-service-name or dbus-object-path) are mutually exclusive" 1136 ); 1137 process::exit(1); 1138 } 1139 _ => { 1140 println!("Please either provide the api-socket option or dbus-service-name and dbus-object-path options"); 1141 process::exit(1); 1142 } 1143 }; 1144 1145 if let Err(e) = target_api.do_command(&matches) { 1146 eprintln!("Error running command: {e}"); 1147 process::exit(1) 1148 }; 1149 } 1150