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