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