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