Skip to content

Commit 914fe19

Browse files
committed
Tests for microvm configuration from command line parameter
Signed-off-by: Laura Loghin <[email protected]>
1 parent 26d3e41 commit 914fe19

File tree

13 files changed

+424
-53
lines changed

13 files changed

+424
-53
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
### Added
4+
5+
- New command-line parameter for both `firecracker` and `jailer`, named
6+
`vmm-config`, which represents a JSON that can be used for configuring and
7+
starting a microVM without sending any API requests.
8+
39
## [0.18.0]
410

511
### Added

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

FAQ.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,9 @@ Possible mitigations are:
196196
kernel so as to use `vmalloc` instead of `kmalloc` for them.
197197
- Reduce memory pressure on the host.
198198

199+
### How can I configure and start a microVM without sending API calls?
200+
201+
Passing an optional command line parameter, `vmm-config`, to the Firecracker process
202+
allows this type of configuration. This parameter must contain the entire JSON that
203+
will be used for configuring all of the microVM's resources. One example of such JSON
204+
can be found in `tests/framework/vm_config.json`.

docs/getting-started.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,34 @@ curl --unix-socket /tmp/firecracker.socket -i \
242242
}'
243243
```
244244

245+
#### Configuring the microVM without sending API requests
246+
247+
If you'd like to boot up a guest machine without using the API socket, you can do that
248+
by passing the parameter `vmm-config` to the Firecracker process. The command for starting
249+
Firecracker with this option will look like this:
250+
251+
```bash
252+
./firecracker --api-sock /tmp/firecracker.socket --vmm-config "JSON_BODY"
253+
```
254+
255+
"JSON_BODY" should contain the entire configuration for all of the microVM's resources.
256+
You **must** provide in your JSON the configuration for the guest kernel and rootfs, as
257+
these are mandatory, but all of the other resources are optional, so it's your choice if
258+
you want to configure them or not. Because using this configuration method will also start
259+
the microVM, you need to specify all desired pre-boot configurable resources in that JSON.
260+
The names of the resources are the ones from the `firecracker.yaml` file and the names of
261+
their fields are the same that are used in API requests.
262+
The easiest way to send the configuration is by storing the JSON in a file and passing
263+
the content of the file as command line parameter, for example:
264+
265+
```bash
266+
./firecracker --api-sock /tmp/firecracker.socket --vmm-config "$(cat /path_to_the_configuration_file)"
267+
```
268+
269+
You can find an example of configuration file in `tests/framework/vm_config.json`.
270+
After the machine is booted, you can still use the socket to send API requests
271+
for post-boot operations.
272+
245273
## Building From Source
246274

247275
The quickest way to build and test Firecracker is by using our development

docs/jailer.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jailer --id <id> \
1414
[--netns <netns>]
1515
[--daemonize]
1616
[--seccomp-level <level>]
17+
[--vmm-config <vmm_config>]
1718
```
1819

1920
- `id` is the unique VM identification string, which may contain alphanumeric
@@ -38,6 +39,8 @@ jailer --id <id> \
3839
Firecracker.
3940
- 2 (default): advanced filtering. This adds further checks on some of the
4041
parameters of the allowed syscalls.
42+
- `vmm_config` represents the json that can be used for configuring and
43+
starting the microVM without using API requests.
4144

4245
## Jailer Operation
4346

@@ -79,12 +82,14 @@ After starting, the Jailer goes through the following operations:
7982
- Drop privileges via setting the provided `uid` and `gid`.
8083
- Exec into `<exec_file_name> --id=<id> --api-sock=/api.socket
8184
--seccomp-level=<level> --start-time-us=<opaque>
82-
--start-time-cpu-us=<opaque>`.
85+
--start-time-cpu-us=<opaque> [--vmm-config <vmm_config>]`.
8386
Where:
8487
- `id`: (`string`) - The `id` argument provided to jailer.
8588
- `level`: (`number`) - the `--seccomp-level` argument provided to jailer.
8689
- `opaque`: (`number`) time calculated by the jailer that it spent doing
8790
its work.
91+
- `vmm_config`: (`string`) - optional argument that can be used for
92+
configuring and starting a microVM without sending API requests.
8893

8994
## Example Run and Notes
9095

@@ -191,6 +196,10 @@ We can now use the socket at
191196
`/srv/jailer/firecracker/551e7604-e35c-42b3-b825-416853441234/root/api.socket`
192197
to interact with the VM.
193198

199+
In case we want to configure the VM without using the API socket, we can also
200+
pass the `vmm-config` parameter, as mentioned in the previous section
201+
(`Jailer Operation`).
202+
194203
### Observations
195204

196205
- The user must create hard links for (or copy) any resources which will be

jailer/src/env.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,7 @@ impl Env {
113113
.parse::<u32>()
114114
.map_err(Error::SeccompLevel)?;
115115

116-
let config_json = match args.value_of("vmm-config") {
117-
Some(s) => Some(String::from(s)),
118-
None => None,
119-
};
116+
let config_json = args.value_of("vmm-config").map(String::from);
120117

121118
Ok(Env {
122119
id: id.to_string(),
@@ -298,7 +295,7 @@ impl Env {
298295
}
299296

300297
let mut command = Command::new(chroot_exec_file);
301-
let init_command = command
298+
command
302299
.arg(format!("--id={}", self.id))
303300
.arg(format!("--seccomp-level={}", self.seccomp_level))
304301
.arg(format!("--start-time-us={}", self.start_time_us))
@@ -311,9 +308,9 @@ impl Env {
311308
.gid(self.gid());
312309

313310
if let Some(json) = self.config_json {
314-
init_command.arg(format!("--vmm-config={}", json));
311+
command.arg(format!("--vmm-config={}", json));
315312
}
316-
Err(Error::Exec(init_command.exec()))
313+
Err(Error::Exec(command.exec()))
317314
}
318315
}
319316

@@ -379,6 +376,25 @@ mod tests {
379376
let gid = "1002";
380377
let chroot_base = "/";
381378
let netns = "zzzns";
379+
let config_json = r#"{
380+
"boot-source":{
381+
"kernel_image_path": "vmlinux.bin",
382+
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
383+
},
384+
"drives": [
385+
{
386+
"drive_id": "rootfs",
387+
"path_on_host": "xenial.rootfs.ext4",
388+
"is_root_device": true,
389+
"is_read_only": false
390+
}
391+
],
392+
"machine-config":{
393+
"vcpu_count": 2,
394+
"mem_size_mib": 1024,
395+
"ht_enabled": false
396+
}
397+
}"#;
382398

383399
// This should be fine.
384400
let good_env = Env::new(
@@ -391,7 +407,7 @@ mod tests {
391407
chroot_base,
392408
Some(netns),
393409
true,
394-
None,
410+
Some(config_json),
395411
),
396412
0,
397413
0,
@@ -408,6 +424,7 @@ mod tests {
408424
assert_eq!(format!("{}", good_env.uid()), uid);
409425
assert_eq!(good_env.netns, Some(netns.to_string()));
410426
assert!(good_env.daemonize);
427+
assert_eq!(good_env.config_json, Some(config_json.to_string()));
411428

412429
let another_good_env = Env::new(
413430
make_args(

src/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,7 @@ fn main() {
148148
.expect("'start-time-cpu_us' parameter expected to be of 'u64' type.")
149149
});
150150

151-
let vmm_config_json = cmd_arguments
152-
.value_of("vmm-config")
153-
.map(|s| String::from(s));
151+
let vmm_config_json = cmd_arguments.value_of("vmm-config").map(String::from);
154152

155153
let shared_info = Arc::new(RwLock::new(InstanceInfo {
156154
state: InstanceState::Uninitialized,

tests/framework/jailer.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __del__(self):
6262
"""Cleanup this jailer context."""
6363
self.cleanup()
6464

65-
def construct_param_list(self):
65+
def construct_param_list(self, config_json):
6666
"""Create the list of parameters we want the jailer to start with.
6767
6868
We want to be able to vary any parameter even the required ones as we
@@ -94,6 +94,8 @@ def construct_param_list(self):
9494
jailer_param_list.extend(
9595
['--seccomp-level', str(self.seccomp_level)]
9696
)
97+
if config_json is not None:
98+
jailer_param_list.extend(['--vmm-config', str(config_json)])
9799
return jailer_param_list
98100

99101
def chroot_base_with_id(self):
@@ -113,16 +115,17 @@ def chroot_path(self):
113115
"""Return the MicroVM chroot path."""
114116
return os.path.join(self.chroot_base_with_id(), 'root')
115117

116-
def jailed_path(self, file_path, create=False):
118+
def jailed_path(self, file_path, create=False, create_jail=False):
117119
"""Create a hard link owned by uid:gid.
118120
119121
Create a hard link to the specified file, changes the owner to
120122
uid:gid, and returns a path to the link which is valid within the jail.
121123
"""
122124
file_name = os.path.basename(file_path)
123125
global_p = os.path.join(self.chroot_path(), file_name)
126+
if create_jail:
127+
os.makedirs(self.chroot_path(), exist_ok=True)
124128
jailed_p = os.path.join("/", file_name)
125-
126129
if create:
127130
cmd = 'ln -f {} {}'.format(file_path, global_p)
128131
run(cmd, shell=True, check=True)

tests/framework/microvm.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def __init__(
4545
microvm_id,
4646
build_feature='',
4747
monitor_memory=True,
48-
bin_cloner_path=None
48+
bin_cloner_path=None,
49+
config_json=None
4950
):
5051
"""Set up microVM attributes, paths, and data structures."""
5152
# Unique identifier for this machine.
@@ -95,6 +96,9 @@ def __init__(
9596
self.machine_cfg = None
9697
self.vsock = None
9798

99+
# Optional json for configuring microvm from command line parameter.
100+
self.config_json = config_json
101+
98102
# The ssh config dictionary is populated with information about how
99103
# to connect to a microVM that has ssh capability. The path of the
100104
# private key is populated by microvms with ssh capabilities and the
@@ -205,9 +209,10 @@ def memory_events_queue(self, queue):
205209
"""Set the memory usage events queue."""
206210
self._memory_events_queue = queue
207211

208-
def create_jailed_resource(self, path):
212+
def create_jailed_resource(self, path, create_jail=False):
209213
"""Create a hard link to some resource inside this microvm."""
210-
return self.jailer.jailed_path(path, create=True)
214+
return self.jailer.jailed_path(path, create=True,
215+
create_jail=create_jail)
211216

212217
def get_jailed_resource(self, path):
213218
"""Get the jailed path to a resource."""
@@ -259,7 +264,7 @@ def spawn(self):
259264
self.network = Network(self._api_socket, self._api_session)
260265
self.vsock = Vsock(self._api_socket, self._api_session)
261266

262-
jailer_param_list = self._jailer.construct_param_list()
267+
jailer_param_list = self._jailer.construct_param_list(self.config_json)
263268

264269
# When the daemonize flag is on, we want to clone-exec into the
265270
# jailer rather than executing it via spawning a shell. Going

tests/framework/vm_config.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"boot-source": {
3+
"kernel_image_path": "vmlinux.bin",
4+
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
5+
},
6+
"logger": {
7+
"log_fifo": "log_fifo",
8+
"metrics_fifo": "metrics_fifo"
9+
},
10+
"drives": [
11+
{
12+
"drive_id": "rootfs",
13+
"path_on_host": "xenial.rootfs.ext4",
14+
"is_root_device": true,
15+
"is_read_only": false
16+
}
17+
],
18+
"machine-config": {
19+
"vcpu_count": 2,
20+
"mem_size_mib": 1024,
21+
"ht_enabled": false
22+
}
23+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Tests microvm start with command line parameter."""
4+
5+
import os
6+
7+
import pytest
8+
9+
import host_tools.logging as log_tools
10+
11+
12+
@pytest.mark.parametrize(
13+
"vm_config_json",
14+
["framework/vm_config.json"]
15+
)
16+
def test_config_json_start(test_microvm_with_ssh, vm_config_json):
17+
"""Start a microvm using configuration sent as command line parameter.
18+
19+
Create resources needed for the configuration of the microvm, then
20+
start a process which receives as command line parameter, one json
21+
for that configuration.
22+
"""
23+
test_microvm = test_microvm_with_ssh
24+
25+
test_microvm.create_jailed_resource(test_microvm.kernel_file,
26+
create_jail=True)
27+
test_microvm.create_jailed_resource(test_microvm.rootfs_file,
28+
create_jail=True)
29+
30+
log_fifo_path = os.path.join(test_microvm.path, 'log_fifo')
31+
metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')
32+
log_fifo = log_tools.Fifo(log_fifo_path)
33+
metrics_fifo = log_tools.Fifo(metrics_fifo_path)
34+
test_microvm.create_jailed_resource(log_fifo.path, create_jail=True)
35+
test_microvm.create_jailed_resource(metrics_fifo.path, create_jail=True)
36+
37+
test_microvm.config_json = open(vm_config_json).read()
38+
test_microvm.spawn()
39+
40+
response = test_microvm.machine_cfg.get()
41+
assert test_microvm.api_session.is_status_ok(response.status_code)

vmm/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ kvm-ioctls = "0.2"
99
libc = ">=0.2.39"
1010
epoll = "=4.0.1"
1111
futures = ">=0.1.18"
12-
hyper = { version = "=0.11.16", default-features = false }
1312
serde = ">=1.0.27"
1413
serde_derive = ">=1.0.27"
1514
serde_json = ">=1.0.9"

0 commit comments

Comments
 (0)