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