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