xref: /cloud-hypervisor/src/bin/ch-remote.rs (revision 9af2968a7dc47b89bf07ea9dc5e735084efcfa3a)
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