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