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