firecracker
1166 строк · 40.3 Кб
1#!/usr/bin/env bash
2
3# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4# SPDX-License-Identifier: Apache-2.0
5
6# Firecracker devtool
7#
8# Use this script to build and test Firecracker.
9#
10# TL;DR
11# Make sure you have Docker installed and properly configured
12# (http://docker.com). Then,
13# building: `./devtool build`
14# Then find the binaries under build/debug/
15# testing: `./devtool test`
16# Will run the entire test battery; will take several minutes to complete.
17# deep-dive: `./devtool shell`
18# Open a shell prompt inside the container. Then build or test (or do
19# anything, really) manually.
20#
21# Still TL;DR: have Docker; ./devtool build; ./devtool test; ./devtool help.
22#
23#
24# Both building and testing are done inside a Docker container. Please make sure
25# you have Docker up and running on your system (see http:/docker.com) and your
26# user has permission to run Docker containers.
27#
28# The Firecracker sources dir will be bind-mounted inside the development
29# container (under /firecracker) and any files generated by the build process
30# will show up under the build/ dir. This includes the final binaries, as well
31# as any intermediate or cache files.
32#
33# By default, all devtool commands run the container transparently, removing
34# it after the command completes. Any persisting files will be stored under
35# build/.
36# If, for any reason, you want to access the container directly, please use
37# `devtool shell`. This will perform the initial setup (bind-mounting the
38# sources dir, setting privileges) and will then drop into a BASH shell inside
39# the container.
40#
41# Building:
42# Run `./devtool build`.
43# By default, the debug binaries are built and placed under build/debug/.
44# To build the release version, run `./devtool build --release` instead.
45# You can then find the binaries under build/release/.
46#
47# Testing:
48# Run `./devtool test`.
49# This will run the entire integration test battery. The testing system is
50# based on pytest (http://pytest.org).
51#
52# Opening a shell prompt inside the development container:
53# Run `./devtool shell`.
54#
55# Additional information:
56# Run `./devtool help`.
57#
58#
59# TODO:
60# - Cache test binaries, preserving them across `./devtool test` invocations.
61# At the moment, Firecracker is rebuilt everytime a test is run.
62# - List tests by parsing the `pytest --collect-only` output.
63# - Implement test filtering with `./devtool test --filter <filter>`
64# - Find an easier way to run individual tests on existing binaries.
65# - Add a `./devtool run` command to set up and run Firecracker.
66# - Add a `./devtool diag` command to help with troubleshooting, by checking
67# the most common failure conditions.
68# - Look into caching the Cargo registry within the container and if that
69# would help with reproducible builds (in addition to pinning Cargo.lock)
70
71# Development container image (without tag)
72DEVCTR_IMAGE_NO_TAG="public.ecr.aws/firecracker/fcuvm"
73
74# Development container tag
75DEVCTR_IMAGE_TAG=${DEVCTR_IMAGE_TAG:-v71}
76
77# Development container image (name:tag)
78# This should be updated whenever we upgrade the development container.
79# (Yet another step on our way to reproducible builds.)
80DEVCTR_IMAGE="${DEVCTR_IMAGE_NO_TAG}:${DEVCTR_IMAGE_TAG}"
81
82# Full path to the Firecracker tools dir on the host.
83FC_TOOLS_DIR=$(cd "$(dirname "$0")" && pwd)
84source "$FC_TOOLS_DIR/functions"
85
86# Full path to the Firecracker sources dir on the host.
87FC_ROOT_DIR=$(cd "${FC_TOOLS_DIR}/.." && pwd)
88
89# Full path to the build dir on the host.
90FC_BUILD_DIR="${FC_ROOT_DIR}/build"
91
92# Full path to devctr dir on the host.
93FC_DEVCTR_DIR="${FC_ROOT_DIR}/tools/devctr"
94
95# Path to the linux kernel directory on the host.
96KERNEL_DIR="${FC_ROOT_DIR}/.kernel"
97
98# Full path to the cargo registry dir on the host. This appears on the host
99# because we want to persist the cargo registry across container invocations.
100# Otherwise, any rust crates from crates.io would be downloaded again each time
101# we build or test.
102CARGO_REGISTRY_DIR="${FC_BUILD_DIR}/cargo_registry"
103
104# Full path to the cargo git registry on the host. This serves the same purpose
105# as CARGO_REGISTRY_DIR, for crates downloaded from GitHub repos instead of
106# crates.io.
107CARGO_GIT_REGISTRY_DIR="${FC_BUILD_DIR}/cargo_git_registry"
108
109# Full path to the cargo target dir on the host.
110CARGO_TARGET_DIR="${FC_BUILD_DIR}/cargo_target"
111
112# Full path to the Firecracker sources dir, as bind-mounted in the container.
113CTR_FC_ROOT_DIR="/firecracker"
114
115# Full path to the build dir, as bind-mounted in the container.
116CTR_FC_BUILD_DIR="${CTR_FC_ROOT_DIR}/build"
117
118# Full path to the cargo target dir, as bind-mounted in the container.
119CTR_CARGO_TARGET_DIR="$CTR_FC_BUILD_DIR/cargo_target"
120
121# Path to the microVM images cache dir
122MICROVM_IMAGES_DIR="build/img"
123
124# Full path to the public key mapping on the guest
125PUB_KEY_PATH=/root/.ssh/id_rsa.pub
126
127# Full path to the private key mapping on the guest
128PRIV_KEY_PATH=/root/.ssh/id_rsa
129
130# Path to the linux kernel directory, as bind-mounted in the container.
131CTR_KERNEL_DIR="${CTR_FC_ROOT_DIR}/.kernel"
132
133# Global options received by $0
134# These options are not command-specific, so we store them as global vars
135OPT_UNATTENDED=false
136
137# Get the target prefix to avoid repeated calls to uname -m
138TARGET_PREFIX="$(uname -m)-unknown-linux-"
139
140
141# Check if Docker is available and exit if it's not.
142# Upon returning from this call, the caller can be certain Docker is available.
143#
144ensure_docker() {
145NEWLINE=$'\n'
146output=$(which docker 2>&1)
147ok_or_die "Docker not found. Aborting." \
148"Please make sure you have Docker (http://docker.com) installed" \
149"and properly configured.${NEWLINE}" \
150"Error: $?, command output: ${output}"
151
152output=$(docker ps 2>&1)
153ok_or_die "Error accessing Docker. Please make sure the Docker daemon" \
154"is running and that you are part of the docker group.${NEWLINE}" \
155"Error: $?, command output: ${output}${NEWLINE}" \
156"For more information, see" \
157"https://docs.docker.com/install/linux/linux-postinstall/"
158}
159
160# Run a command and retry multiple times if it fails. Once it stops
161# failing return to normal execution. If there are "retry count"
162# failures, set the last error code.
163# $1 - command
164# $2 - retry count
165# $3 - sleep interval between retries
166retry_cmd() {
167command=$1
168retry_cnt=$2
169sleep_int=$3
170
171{
172$command
173} || {
174# Command failed, substract one from retry_cnt
175retry_cnt=$((retry_cnt - 1))
176
177# If retry_cnt is larger than 0, sleep and call again
178if [ "$retry_cnt" -gt 0 ]; then
179echo "$command failed, retrying..."
180sleep "$sleep_int"
181retry_cmd "$command" "$retry_cnt" "$sleep_int"
182fi
183}
184}
185
186# Attempt to download our Docker image. Exit if that fails.
187# Upon returning from this call, the caller can be certain our Docker image is
188# available on this system.
189#
190ensure_devctr() {
191
192# We depend on having Docker present.
193ensure_docker
194
195# Check if we have the container image available locally. Attempt to
196# download it, if we don't.
197[[ $(docker images -q "$DEVCTR_IMAGE" | wc -l) -gt 0 ]] || {
198say "About to pull docker image $DEVCTR_IMAGE"
199get_user_confirmation || die "Aborted."
200
201# Run docker pull 5 times in case it fails - sleep 3 seconds
202# between attempts
203retry_cmd "docker pull $DEVCTR_IMAGE" 5 3
204
205ok_or_die "Error pulling docker image. Aborting."
206}
207}
208
209# Make sure the build/ dirs are available. Exit if we can't create them.
210# Upon returning from this call, the caller can be certain the build/ dirs exist.
211#
212ensure_build_dir() {
213for dir in "$FC_BUILD_DIR" "$CARGO_TARGET_DIR" \
214"$CARGO_REGISTRY_DIR" "$CARGO_GIT_REGISTRY_DIR"; do
215create_dir "$dir"
216done
217}
218
219build_bin_path() {
220target="$1"
221profile="$2"
222binary="$3"
223echo "$CARGO_TARGET_DIR/$target/$profile/$binary"
224}
225
226# Fix build/ dir permissions after a privileged container run.
227# Since the privileged container runs as root, any files it creates will be
228# owned by root. This fixes that by recursively changing the ownership of build/
229# to the current user.
230#
231cmd_fix_perms() {
232# Yes, running Docker to get elevated privileges, just to chown some files
233# is a dirty hack.
234run_devctr \
235-- \
236chown -R "$(id -u):$(id -g)" "$CTR_FC_BUILD_DIR"
237}
238
239# Builds the development container from its Dockerfile.
240#
241cmd_build_devctr() {
242docker_file_name=$FC_DEVCTR_DIR/Dockerfile
243build_args="--build-arg ARCH=$(uname -m)"
244
245while [ $# -gt 0 ]; do
246case "$1" in
247"-h"|"--help") { cmd_help; exit 1; } ;;
248"--") { shift; break; } ;;
249*)
250die "Unknown argument: $1. Please use --help for help."
251;;
252esac
253shift
254done
255
256docker build -t "$DEVCTR_IMAGE_NO_TAG" -f "$docker_file_name" $build_args .
257}
258
259
260# Validate the user supplied kernel version number.
261# It must be composed of 2 groups of integers separated by dot, with an optional third group.
262validate_kernel_version() {
263local version_regex="^([0-9]+.)[0-9]+(.[0-9]+)?$"
264version="$1"
265
266if [ -z "$version" ]; then
267die "Kernel version cannot be empty."
268elif [[ ! "$version" =~ $version_regex ]]; then
269die "Invalid version number: $version (expected: \$Major.\$Minor.\$Patch(optional))."
270fi
271
272}
273
274
275# Helper function to run the dev container.
276# Usage: run_devctr <docker args> -- <container args>
277# Example: run_devctr --privileged -- bash -c "echo 'hello world'"
278run_devctr() {
279docker_args=()
280ctr_args=()
281docker_args_done=false
282while [[ $# -gt 0 ]]; do
283[[ "$1" = "--" ]] && {
284docker_args_done=true
285shift
286continue
287}
288[[ $docker_args_done = true ]] && ctr_args+=("$1") || docker_args+=("$1")
289shift
290done
291
292# If we're running in a terminal, pass the terminal to Docker and run
293# the container interactively
294[[ -t 0 ]] && docker_args+=("-i")
295[[ -t 1 ]] && docker_args+=("-t")
296
297# Try to pass these environments from host into container for network proxies
298proxies=(http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY)
299for i in "${proxies[@]}"; do
300if [[ ! -z ${!i} ]]; then
301docker_args+=("--env") && docker_args+=("$i=${!i}")
302fi
303done
304
305# Finally, run the dev container
306# Use 'z' on the --volume parameter for docker to automatically relabel the
307# content and allow sharing between containers.
308docker run "${docker_args[@]}" \
309--rm \
310--volume /dev:/dev \
311--volume "$FC_ROOT_DIR:$CTR_FC_ROOT_DIR:z" \
312--tmpfs /srv:exec,dev,size=32G \
313-v /boot:/boot \
314--env PYTHONDONTWRITEBYTECODE=1 \
315"$DEVCTR_IMAGE" "${ctr_args[@]}"
316}
317
318# Helper function to test that the argument provided is a valid path to a SSH key.
319#
320test_key() {
321ssh-keygen -lf "$1" &>/dev/null
322ret=$?
323[ $ret -ne 0 ] && die "$1 is not a valid key file."
324}
325
326create_dir() {
327# Create a dir for the provided path.
328dir="$1"
329mkdir -p "$dir" || die "Error: cannot create dir $dir"
330[ -x "$dir" ] && [ -w "$dir" ] || \
331{
332say "Wrong permissions for $dir. Attempting to fix them ..."
333chmod +x+w "$dir"
334} || \
335die "Error: wrong permissions for $dir. Should be +x+w"
336}
337
338# `$0 help`
339# Show the detailed devtool usage information.
340#
341cmd_help() {
342echo ""
343echo "Firecracker $(basename $0)"
344echo "Usage: $(basename $0) [<args>] <command> [<command args>]"
345echo ""
346echo "Global arguments"
347echo " -y, --unattended Run unattended. Assume the user would always"
348echo " answer \"yes\" to any confirmation prompt."
349echo ""
350echo "Available commands:"
351echo ""
352echo " build [--debug|--release] [-l|--libc musl|gnu] [-- [<cargo args>]]"
353echo " Build the Firecracker binaries."
354echo " Firecracker is built using the Rust build system (cargo). All arguments after --"
355echo " will be passed through to cargo."
356echo " --debug Build the debug binaries. This is the default."
357echo " --release Build the release binaries."
358echo " -l, --libc musl|gnu Choose the libc flavor against which Firecracker will"
359echo " be linked. Default is musl."
360echo " --ssh-keys Provide the paths to the public and private SSH keys on the host"
361echo " (in this particular order) required for the git authentication."
362echo " It is mandatory that both keys are specified."
363echo ""
364echo " build_devctr"
365echo " Builds the development container from its Dockerfile."
366echo ""
367echo " checkenv"
368echo " Performs prerequisites checks needed to execute firecracker."
369echo ""
370echo " distclean"
371echo " Clean up the build tree and remove the docker container."
372echo ""
373echo " fix_perms"
374echo " Fixes permissions when devtool dies in the middle of a privileged session."
375echo ""
376echo " fmt"
377echo " Auto-format all Rust source files, to match the Firecracker requirements."
378echo " This should be used as the last step in every commit, to ensure that the"
379echo " Rust style tests pass."
380echo ""
381echo " generate_syscall_tables <version>"
382echo " Generates the syscall tables for seccompiler, according to a given kernel version."
383echo " Release candidate (rc) linux versions are not allowed."
384echo " Outputs a rust file for each supported arch: src/seccompiler/src/syscall_table/{arch}.rs"
385echo " Supported architectures: x86_64 and aarch64."
386echo ""
387echo " install [-p|--path] [--debug|--release]"
388echo " Install firecracker, jailer and seccomp binaries to /usr/local/bin or a given path."
389echo " Only the musl linked binaries are supported."
390echo " --path Install binaries to a specified path."
391echo " --debug Install the debug binaries."
392echo " --release Install the release binaries. This is the default."
393echo ""
394echo " help"
395echo " Display this help message."
396echo ""
397echo " shell [--privileged]"
398echo " Launch the development container and open an interactive BASH shell."
399echo " -p, --privileged Run the container as root, in privileged mode."
400echo " Running Firecracker via the jailer requires elevated"
401echo " privileges, though the build phase does not."
402echo ""
403echo " sh CMD..."
404echo " Launch the development container and run a command."
405echo ""
406echo " test [-- [<pytest args>]]"
407echo " Run the Firecracker integration tests."
408echo " The Firecracker testing system is based on pytest. All arguments after --"
409echo " will be passed through to pytest."
410echo " -c, --cpuset-cpus cpulist Set a dedicated cpulist to be used by the tests."
411echo " -m, --cpuset-mems memlist Set a dedicated memlist to be used by the tests."
412echo " --performance Tweak various setting of the host running the tests (such as C- and P-states)"
413echo " to achieve consistent performance. Used for running performance tests in CI."
414echo ""
415
416cat <<EOF
417test_debug [-- [<pytest args>]]
418Run tests in a debugging environment
419
420test_sandbox
421Run Firecracker in an IPython REPL
422
423mkdocs
424Use 'cargo doc' to generate rustdoc documentation
425
426checkstyle
427Run style checks
428EOF
429}
430
431
432# `$0 build` - build Firecracker
433# Please see `$0 help` for more information.
434#
435cmd_build() {
436# By default, we'll build the debug binaries.
437profile="debug"
438libc="musl"
439
440# Parse any command line args.
441while [ $# -gt 0 ]; do
442case "$1" in
443"-h"|"--help") { cmd_help; exit 1; } ;;
444"--debug") { profile="debug"; } ;;
445"--release") { profile="release"; } ;;
446"--ssh-keys")
447shift
448[[ -z "$1" ]] && \
449die "Please provide the path to the public SSH key."
450[[ ! -f "$1" ]] && die "The public key file does not exist: $1."
451test_key "$1"
452host_pub_key_path="$1"
453shift
454[[ -z "$1" ]] && \
455die "Please provide the path to the private SSH key."
456[[ ! -f "$1" ]] && die "The private key file does not exist: $1."
457test_key "$1"
458host_priv_key_path="$1"
459;;
460"-l"|"--libc")
461shift
462[[ "$1" =~ ^(musl|gnu)$ ]] || \
463die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
464libc="$1"
465;;
466"--") { shift; break; } ;;
467*)
468die "Unknown argument: $1. Please use --help for help."
469;;
470esac
471shift
472done
473
474# Check prerequisites
475ensure_devctr
476ensure_build_dir
477
478# Map the public and private keys to the guest if they are specified.
479[ ! -z "$host_pub_key_path" ] && [ ! -z "$host_priv_key_path" ] &&
480extra_args="--volume $host_pub_key_path:$PUB_KEY_PATH:z \
481--volume $host_priv_key_path:$PRIV_KEY_PATH:z"
482
483# Run the cargo build process inside the container.
484# We don't need any special privileges for the build phase, so we run the
485# container as the current user/group.
486run_devctr \
487--user "$(id -u):$(id -g)" \
488--workdir "$CTR_FC_ROOT_DIR" \
489${extra_args} \
490-- \
491./tools/release.sh --libc $libc --profile $profile
492ret=$?
493return $ret
494}
495
496function cmd_make_release {
497run_devctr \
498--user "$(id -u):$(id -g)" \
499--workdir "$CTR_FC_ROOT_DIR" \
500-- \
501./tools/release.sh --libc musl --profile release --make-release
502}
503
504cmd_distclean() {
505# List of folders to remove.
506dirs=("build" "test_results")
507
508for dir in "${dirs[@]}"; do
509if [ -d "$dir" ]; then
510say "Removing $dir"
511rm -rf "$dir"
512fi
513done
514
515# Remove devctr if it exists
516if [ $(docker images -q "$DEVCTR_IMAGE" | wc -l) -eq "1" ]; then
517say "Removing $DEVCTR_IMAGE"
518docker rmi -f "$DEVCTR_IMAGE"
519fi
520}
521
522ensure_ci_artifacts() {
523if ! command -v aws >/dev/null; then
524die "AWS CLI not installed, which is required for downloading artifacts for integration tests."
525fi
526
527# Fetch all the artifacts so they are local
528say "Fetching CI artifacts from S3"
529S3_URL=s3://spec.ccfc.min/firecracker-ci/v1.7/$(uname -m)
530ARTIFACTS=$MICROVM_IMAGES_DIR/$(uname -m)
531if [ ! -d "$ARTIFACTS" ]; then
532mkdir -pv $ARTIFACTS
533aws s3 sync --no-sign-request "$S3_URL" "$ARTIFACTS"
534# fix permissions
535find "$ARTIFACTS" -type f -name "*.id_rsa" |xargs chmod -c 400
536find "$ARTIFACTS/firecracker" -type f |xargs chmod -c 755
537fi
538}
539
540apply_linux_61_tweaks() {
541KV=$(uname -r)
542if [[ $KV != 6.1.* ]] || [ $(uname -m) != x86_64 ]; then
543return
544fi
545say "Applying Linux 6.1 boot-time regression mitigations"
546
547KVM_VENDOR_MOD=$(lsmod |grep -P "^kvm_(amd|intel)" | awk '{print $1}')
548ITLB_MULTIHIT=/sys/devices/system/cpu/vulnerabilities/itlb_multihit
549NX_HUGEPAGES=/sys/module/kvm/parameters/nx_huge_pages
550
551# If m6a/m6i
552if grep -q "Not affected" $ITLB_MULTIHIT; then
553echo -e "CPU not vulnerable to iTLB multihit, using kvm.nx_huge_pages=never mitigation"
554# we need a lock so another process is not running the same thing and to
555# avoid race conditions.
556lockfile="/tmp/.linux61_tweaks.lock"
557set -C # noclobber
558while true; do
559if echo "$$" > "$lockfile"; then
560echo "Successfully acquired lock"
561if ! grep -q "never" $NX_HUGEPAGES; then
562echo "Reloading KVM modules with nx_huge_pages=never"
563sudo modprobe -r $KVM_VENDOR_MOD kvm
564sudo modprobe kvm nx_huge_pages=never
565sudo modprobe $KVM_VENDOR_MOD
566fi
567rm "$lockfile"
568break
569else
570sleep 5s
571fi
572done
573tail -v $ITLB_MULTIHIT $NX_HUGEPAGES
574# else (m5d Skylake and CascadeLake)
575else
576echo "CPU vulnerable to iTLB_multihit, checking if favordynmods is enabled"
577mount |grep cgroup |grep -q favordynmods
578if [ $? -ne 0 ]; then
579say_warn "cgroups' favordynmods option not enabled; VM creation performance may be impacted"
580else
581echo "favordynmods is enabled"
582fi
583fi
584}
585
586
587# Modifies the processors C- and P-state configuration (x86_64 only) for consistent performance. This means
588# - Disable turbo boost (Intel only) by writing 1 to /sys/devices/system/cpu/intel_pstate/no_turbo
589# - Disable turbo boost (AMD only) by writing 0 to /sys/devices/system/cpu/cpufreq/boost
590# - Lock the CPUs' P-state to the highest non-turbo one (Intel only) by writing 100 to /sys/devices/system/cpu/intel_pstate/{min,max}_perf_pct
591# - Disable all idle C-states, meaning all CPu cores will idle by polling (busy looping) by writing 1 to /sys/devices/system/cpu/cpu*/cpuidle/state*/disable
592# - Set the cpu frequency governor to performance by writing "performance" to /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
593apply_performance_tweaks() {
594# m6a instances do not support the amd_pstate driver (yet), so nothing we can do there
595if [[ -d /sys/devices/system/cpu/intel_pstate ]]; then
596# Disable turbo boost. Some of our tests are performance tests, and we want minimum variability wrt processor frequency
597# See also https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/processor_state_control.html
598echo 1 |sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo &> /dev/null
599
600# Save old values to restore later
601MIN_PERF_PCT=$(cat /sys/devices/system/cpu/intel_pstate/min_perf_pct)
602MAX_PERF_PCT=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct)
603
604# Force the CPU to continuously stay in the highest, non-turbo P-state. The P-state will determine the
605# CPU's clock frequency.
606# https://www.kernel.org/doc/html/v4.12/admin-guide/pm/intel_pstate.html
607echo 100 |sudo tee /sys/devices/system/cpu/intel_pstate/min_perf_pct &> /dev/null
608echo 100 |sudo tee /sys/devices/system/cpu/intel_pstate/max_perf_pct &> /dev/null
609elif [[ -f /sys/devices/system/cpu/cpufreq/boost ]] && [ ! $(uname -r | grep 4.14) ]; then
610echo 0 |sudo tee /sys/devices/system/cpu/cpufreq/boost &> /dev/null
611fi
612
613# The governor is a linux component that can adjust CPU frequency. "performance" tells it to always run CPUs at
614# their maximum safe frequency. It seems to be the default for Amazon Linux, but it doesn't hurt to make this explicit.
615# See also https://wiki.archlinux.org/title/CPU_frequency_scaling
616echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor &> /dev/null
617
618# When a CPU core has nothing to do, it enters an idle state, also called "C-state". These are enumerated, with C0
619# being the shallowest idle state (corresponding to "currently executing instructions", aka "not actually idling"),
620# and higher numbers being deeper sleep states (how many there are depends on the specific processor). The deeper
621# a C-state a CPU core enters, the higher the latency to wake it up again. We can disable deeper C-states altogether
622# by forcing each CPU core to constantly stay in C-0 (e.g. have them actively poll for new things to do).
623# See also https://www.kernel.org/doc/html/v5.0/admin-guide/pm/cpuidle.html.
624# The below also set "disable=1" on "state0", but this does not do anything (as disabling C-0 makes no sense).
625echo 1 |sudo tee /sys/devices/system/cpu/cpu*/cpuidle/state*/disable &> /dev/null
626}
627
628unapply_performance_tweaks() {
629if [[ -d /sys/devices/system/cpu/intel_pstate ]]; then
630# reenable turbo boost
631echo 0 |sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo &> /dev/null
632
633# restore p-state limits
634echo $MIN_PERF_PCT |sudo tee /sys/devices/system/cpu/intel_pstate/min_perf_pct &> /dev/null
635echo $MAX_PERF_PCT |sudo tee /sys/devices/system/cpu/intel_pstate/max_perf_pct &> /dev/null
636elif [[ -f /sys/devices/system/cpu/cpufreq/boost ]]; then
637echo 1 | sudo tee /sys/devices/system/cpu/cpufreq/boost &> /dev/null
638fi
639
640# reenable deeper sleep states
641echo 0 | sudo tee /sys/devices/system/cpu/cpu*/cpuidle/state*/disable &>/dev/null
642
643# We do not reset the governor, as keeping track of each CPUs configured governor is not trivial here. On our CI
644# instances, the performance governor is current the default anyway (2023/11/14)
645}
646
647
648# `$0 test` - run integration tests
649# Please see `$0 help` for more information.
650#
651cmd_test() {
652do_ab_test=0
653# Parse any command line args.
654while [ $# -gt 0 ]; do
655case "$1" in
656"-h"|"--help") { cmd_help; exit 1; } ;;
657"-c"|"--cpuset-cpus")
658shift
659local cpuset_cpus="$1"
660;;
661"-m"|"--cpuset-mems")
662shift
663local cpuset_mems="$1"
664;;
665"--performance")
666local performance_tweaks=1;
667;;
668"--ab")
669do_ab_test=1
670;;
671"--") { shift; break; } ;;
672*)
673die "Unknown argument: $1. Please use --help for help."
674;;
675esac
676shift
677done
678
679# Check prerequisites.
680ensure_kvm
681ensure_devctr
682ensure_build_dir
683ensure_ci_artifacts
684
685apply_linux_61_tweaks
686
687# If we got to here, we've got all we need to continue.
688say "Kernel version: $(uname -r)"
689say "$(sed '/^processor.*: 0$/,/^processor.*: 1$/!d; /^processor.*: 1$/d' /proc/cpuinfo)"
690say "RPM microcode_ctl version: $(rpm -q microcode_ctl)"
691
692env |grep -P "^(AWS_EMF_|BUILDKITE|CODECOV_)" > env.list
693if [[ $performance_tweaks -eq 1 ]]; then
694if [[ "$(uname --machine)" == "x86_64" ]]; then
695say "Detected CI and performance tests, tuning CPU frequency scaling and idle states for reduced variability"
696
697apply_performance_tweaks
698fi
699
700# It seems that even if the tests using huge pages run sequentially on ag=1 agents, right-sizing the huge pages
701# pool to the total number of huge pages used across all tests results in spurious failures with pool depletion
702# anyway (something else on the host seems to be stealing our huge pages, and we cannot "ear mark" them for
703# Firecracker processes). Thus, just allocate 4GB of them and call it a day.
704say "Setting up huge pages pool"
705num_hugetlbfs_pages=2048
706
707huge_pages_old=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)
708huge_pages_new=$(echo $num_hugetlbfs_pages |sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)
709fi
710
711if [[ "$huge_pages_new" -ne "$num_hugetlbfs_pages" ]]; then
712die "Failed to allocate $num_hugetlbfs_pages hugetlbfs pages, only got $huge_pages_new"
713fi
714
715say "Starting test run ..."
716
717test_script="./tools/test.sh"
718
719if [ $do_ab_test -eq 1 ]; then
720test_script="./tools/ab_test.py"
721fi
722
723# Testing (running Firecracker via the jailer) needs root access,
724# in order to set-up the Firecracker jail (manipulating cgroups, net
725# namespaces, etc).
726# We need to run a privileged container to get that kind of access.
727run_devctr \
728--privileged \
729--security-opt seccomp=unconfined \
730--ulimit core=0 \
731--ulimit nofile=4096:4096 \
732--ulimit memlock=-1:-1 \
733--workdir "$CTR_FC_ROOT_DIR" \
734--cpuset-cpus="$cpuset_cpus" \
735--cpuset-mems="$cpuset_mems" \
736--env-file env.list \
737-- \
738$test_script "$@"
739
740ret=$?
741
742say "Finished test run ..."
743
744# Running as root would have created some root-owned files under the build
745# dir. Let's fix that.
746cmd_fix_perms
747
748# undo performance tweaks (in case the instance gets recycled for a non-perf test)
749if [[ $performance_tweaks -eq 1 ]]; then
750if [[ "$(uname --machine)" == "x86_64" ]]; then
751unapply_performance_tweaks
752fi
753
754echo $huge_pages_old |sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages >/dev/null
755fi
756
757# do not leave behind env.list file
758rm env.list
759
760return $ret
761}
762
763
764# `$0 shell` - drop to a shell prompt inside the dev container
765# Please see `$0 help` for more information.
766#
767cmd_shell() {
768
769# By default, we run the container as the current user.
770privileged=false
771
772# Parse any command line args.
773while [ $# -gt 0 ]; do
774case "$1" in
775"-h"|"--help") { cmd_help; exit 1; } ;;
776"-p"|"--privileged") { privileged=true; } ;;
777"--") { shift; break; } ;;
778*)
779die "Unknown argument: $1. Please use --help for help."
780;;
781esac
782shift
783done
784
785# Make sure we have what we need to continue.
786ensure_devctr
787ensure_build_dir
788
789if [[ $privileged = true ]]; then
790# If requested, spin up a privileged container.
791#
792say "Dropping to a privileged shell prompt ..."
793say "Note: $FC_ROOT_DIR is bind-mounted under $CTR_FC_ROOT_DIR"
794say_warn "You are running as root; any files that get created under" \
795"$CTR_FC_ROOT_DIR will be owned by root."
796run_devctr \
797--privileged \
798--ulimit nofile=4096:4096 \
799--ulimit memlock=-1:-1 \
800--security-opt seccomp=unconfined \
801--workdir "$CTR_FC_ROOT_DIR" \
802-- \
803bash
804ret=$?
805
806# Running as root may have created some root-owned files under the build
807# dir. Let's fix that.
808#
809cmd_fix_perms
810else
811say "Dropping to shell prompt as user $(whoami) ..."
812say "Note: $FC_ROOT_DIR is bind-mounted under $CTR_FC_ROOT_DIR"
813say_warn "You won't be able to run Firecracker via the jailer," \
814"but you can still build it."
815say "You can use \`$0 shell --privileged\` to get a root shell."
816
817[ -w /dev/kvm ] || \
818say_warn "WARNING: user $(whoami) doesn't have permission to" \
819"access /dev/kvm. You won't be able to run Firecracker."
820
821run_devctr \
822--user "$(id -u):$(id -g)" \
823--ulimit nofile=4096:4096 \
824--ulimit memlock=-1:-1 \
825--device=/dev/kvm:/dev/kvm \
826--workdir "$CTR_FC_ROOT_DIR" \
827--env PS1="$(whoami)@\h:\w\$ " \
828-- \
829bash --norc
830ret=$?
831fi
832
833return $ret
834}
835
836cmd_sh() {
837ensure_build_dir
838ensure_ci_artifacts
839run_devctr \
840--privileged \
841--ulimit nofile=4096:4096 \
842--ulimit memlock=-1:-1 \
843--workdir "$CTR_FC_ROOT_DIR" \
844-- \
845bash --norc -c "$*"
846}
847
848cmd_sandbox() {
849cmd_sh "tmux new env PYTEST_ADDOPTS=--pdbcls=IPython.terminal.debugger:TerminalPdb PYTHONPATH=tests IPYTHONDIR=\$PWD/.ipython ipython -i ./tools/sandbox.py $@"
850}
851
852cmd_test_debug() {
853cmd_sh "tmux new ./tools/test.sh --pdb $@"
854}
855
856# Auto-format all source code, to match the Firecracker requirements. For the
857# moment, this is just a wrapper over `cargo fmt --all`
858# Example: `devtool fmt`
859#
860cmd_fmt() {
861cmd_sh "cargo fmt --all -- --config $(tr '\n' ',' <tests/fmt.toml)"
862}
863
864cmd_mkdocs() {
865cmd_sh "cargo doc --workspace --no-deps --document-private-items"
866}
867
868cmd_checkstyle() {
869cmd_test -- integration_tests/style -n 4 --dist worksteal
870}
871
872# Check if able to run firecracker.
873# ../docs/getting-started.md#prerequisites
874ensure_kvm_rw () {
875[[ -c /dev/kvm && -w /dev/kvm && -r /dev/kvm ]] || \
876say_err "FAILED: user $(whoami) doesn't have permission to" \
877"access /dev/kvm."
878}
879
880check_kernver () {
881KERN_MAJOR=4
882KERN_MINOR=14
883(uname -r | awk -v MAJOR=$KERN_MAJOR -v MINOR=$KERN_MINOR '{ split($0,kver,".");
884if( (kver[1] + (kver[2] / 100) ) < MAJOR + (MINOR/100) )
885{
886exit 1;
887} }') ||
888say_err "FAILED: Kernel version must be >= $KERN_MAJOR.$KERN_MINOR"
889}
890
891# Check Production Host Setup
892# ../docs/prod-host-setup.md
893
894check_KPTI () {
895(grep -q "^Mitigation: PTI$" \
896/sys/devices/system/cpu/vulnerabilities/meltdown) || \
897say_warn "WARNING: KPTI NOT SUPPORTED."
898}
899
900check_KSM () {
901(grep -q "^0$" /sys/kernel/mm/ksm/run) || \
902say_warn "WARNING: KSM ENABLED."
903}
904
905check_vulns () {
906for f in /sys/devices/system/cpu/vulnerabilities/* ; do
907if $(grep -q "Vulnerable" ${f}) ; then
908say_warn "WARNING: `basename $f`: VULNERABLE.";
909fi
910done
911}
912
913check_swap () {
914(grep -q "swap.img" /proc/swaps ) && \
915say_warn "WARNING: SWAP ENABLED."
916}
917
918check_EPT() {
919if [ "$(uname --machine)" = "x86_64" ]; then
920(grep -q "Y" /sys/module/kvm_intel/parameters/ept ; [ $? -ne 1 ]) || \
921say_warn "WARNING: EPT DISABLED. Performance will be affected."
922fi
923}
924
925check_vm() {
926if [ $(dmesg | grep -c -i "hypervisor detected") -gt 0 ]; then
927say_warn "WARNING: you are running in a virtual machine." \
928"Firecracker is not well tested under nested virtualization."
929fi
930}
931
932cmd_checkenv() {
933# Parse any command line args.
934while [ $# -gt 0 ]; do
935case "$1" in
936"-h"|"--help") { cmd_help; exit 1; } ;;
937*)
938die "Unknown argument: $1. Please use --help for help."
939;;
940esac
941shift
942done
943PROD_DOC="../docs/prod-host-setup.md"
944QUICKSTART="../docs/getting-started.md#prerequisites"
945say "Checking prerequisites for running Firecracker."
946say "Please check $QUICKSTART in case of any error."
947ensure_kvm_rw
948check_kernver
949check_vm
950say "Checking Host Security Configuration."
951say "Please check $PROD_DOC in case of any error."
952check_KSM
953check_swap
954check_EPT
955check_vulns
956}
957
958generate_syscall_table_x86_64() {
959path_to_rust_file="$FC_ROOT_DIR/src/seccompiler/src/syscall_table/x86_64.rs"
960
961echo "$header" > $path_to_rust_file
962
963# the table for x86_64 is nicely formatted here: linux/arch/x86/entry/syscalls/syscall_64.tbl
964cat linux/arch/x86/entry/syscalls/syscall_64.tbl | grep -v "^#" | grep -v -e '^$' |\
965awk '{print $2,$3,$1}' | grep -v "^x32" |\
966awk '{print " map.insert(\""$2"\".to_string(), "$3");"}' | sort >> $path_to_rust_file
967
968echo "$footer" >> $path_to_rust_file
969
970say "Generated at: $path_to_rust_file"
971}
972
973generate_syscall_table_aarch64() {
974path_to_rust_file="$FC_ROOT_DIR/src/seccompiler/src/syscall_table/aarch64.rs"
975
976# filter for substituting `#define`s that point to other macros;
977# values taken from linux/include/uapi/asm-generic/unistd.h
978replace+='s/__NR3264_fadvise64/223/;'
979replace+='s/__NR3264_fcntl/25/;'
980replace+='s/__NR3264_fstatat/79/;'
981replace+='s/__NR3264_fstatfs/44/;'
982replace+='s/__NR3264_fstat/80/;'
983replace+='s/__NR3264_ftruncate/46/;'
984replace+='s/__NR3264_lseek/62/;'
985replace+='s/__NR3264_sendfile/71/;'
986replace+='s/__NR3264_statfs/43/;'
987replace+='s/__NR3264_truncate/45/;'
988replace+='s/__NR3264_mmap/222/;'
989
990echo "$header" > $path_to_rust_file
991
992# run the gcc command in the Docker container (to make sure that we have gcc installed)
993# the aarch64 syscall table is not located in a .tbl file, like x86; we run gcc's
994# pre-processor to extract the numeric constants from header files.
995run_devctr \
996--user "$(id -u):$(id -g)" \
997--workdir "$CTR_KERNEL_DIR" \
998-- \
999gcc -Ilinux/include/uapi -E -dM -D__ARCH_WANT_RENAMEAT\
1000-D__BITS_PER_LONG=64\
1001linux/arch/arm64/include/uapi/asm/unistd.h |\
1002grep "#define __NR_" | grep -v "__NR_syscalls" |\
1003grep -v "__NR_arch_specific_syscall" |\
1004awk -F '__NR_' '{print $2}' |\
1005sed $replace |\
1006awk '{ print " map.insert(\""$1"\".to_string(), "$2");" }' |\
1007sort -d >> $path_to_rust_file
1008ret=$?
1009
1010[ $ret -ne 0 ] && return $ret
1011
1012echo "$footer" >> $path_to_rust_file
1013
1014say "Generated at: $path_to_rust_file"
1015}
1016
1017cmd_generate_syscall_tables() {
1018# Parse any command line args.
1019while [ $# -gt 0 ]; do
1020case "$1" in
1021"-h"|"--help") { cmd_help; exit 1; } ;;
1022*) { kernel_version="$1"; break; } ;;
1023esac
1024shift
1025done
1026
1027validate_kernel_version "$kernel_version"
1028
1029kernel_major=v$(echo ${kernel_version} | cut -d . -f 1).x
1030kernel_baseurl=https://www.kernel.org/pub/linux/kernel/${kernel_major}
1031kernel_archive=linux-${kernel_version}.tar.xz
1032
1033ensure_devctr
1034
1035# Create the kernel clone directory
1036rm -rf "$KERNEL_DIR"
1037create_dir "$KERNEL_DIR"
1038cd "$KERNEL_DIR"
1039
1040say "Fetching linux kernel..."
1041
1042# Get sha256 checksum.
1043curl -fsSLO ${kernel_baseurl}/sha256sums.asc && \
1044kernel_sha256=$(grep ${kernel_archive} sha256sums.asc | cut -d ' ' -f 1)
1045# Get kernel archive.
1046curl -fsSLO "$kernel_baseurl/$kernel_archive" && \
1047# Verify checksum.
1048echo "${kernel_sha256} ${kernel_archive}" | sha256sum -c - && \
1049# Decompress the kernel source.
1050xz -d "${kernel_archive}" && \
1051cat linux-${kernel_version}.tar | tar -x && mv linux-${kernel_version} linux
1052
1053ret=$?
1054[ $ret -ne 0 ] && return $ret
1055
1056# rust file header
1057read -r -d '' header << EOM
1058// Copyright $(date +"%Y") Amazon.com, Inc. or its affiliates. All Rights Reserved.
1059// SPDX-License-Identifier: Apache-2.0
1060
1061// This file is auto-generated by \`tools/devtool generate_syscall_tables\`.
1062// Do NOT manually edit!
1063// Generated at: $(date)
1064// Kernel version: $kernel_version
1065
1066use std::collections::HashMap;
1067
1068pub(crate) fn make_syscall_table(map: &mut HashMap<String, i64>) {
1069EOM
1070
1071# rust file footer
1072read -r -d '' footer << EOM
1073}
1074
1075EOM
1076
1077# generate syscall table for x86_64
1078say "Generating table for x86_64..."
1079generate_syscall_table_x86_64 $header $footer
1080
1081# generate syscall table for aarch64
1082say "Generating table for aarch64..."
1083generate_syscall_table_aarch64 $header $footer
1084
1085ret=$?
1086[ $ret -ne 0 ] && return $ret
1087}
1088
1089cmd_install() {
1090# By default we install release/musl binaries.
1091profile="release"
1092target="$TARGET_PREFIX""musl"
1093install_path="/usr/local/bin"
1094binaries=("firecracker" "jailer" "seccompiler-bin" "rebase-snap" "cpu-template-helper")
1095
1096# Parse any command line args.
1097while [ $# -gt 0 ]; do
1098case "$1" in
1099"-h"|"--help") { cmd_help; exit 1; } ;;
1100"-p"|"--path")
1101shift;
1102install_path=$1;
1103;;
1104"--debug") { profile="debug"; } ;;
1105"--release") { profile="release"; } ;;
1106*)
1107die "Unknown argument: $1. Please use --help for help."
1108;;
1109esac
1110shift
1111done
1112
1113# Check that the binaries exist first
1114for binary in "${binaries[@]}"; do
1115bin_path=$( build_bin_path "$target" "$profile" "$binary" )
1116if [ ! -f "$bin_path" ]; then
1117die "Missing release binary. Needed file: $bin_path\n"\
1118"To build the binaries, run:\n\t$0 build --$profile"
1119fi
1120done
1121
1122# Install the binaries
1123for binary in "${binaries[@]}"; do
1124say "Installing $binary in $install_path"
1125install -m 755 "$( build_bin_path "$target" "$profile" "$binary" )" "$install_path"
1126done
1127}
1128
1129
1130main() {
1131
1132if [ $# = 0 ]; then
1133die "No command provided. Please use \`$0 help\` for help."
1134fi
1135
1136# Parse main command line args.
1137#
1138while [ $# -gt 0 ]; do
1139case "$1" in
1140-h|--help) { cmd_help; exit 1; } ;;
1141-y|--unattended) { OPT_UNATTENDED=true; } ;;
1142-*)
1143die "Unknown arg: $1. Please use \`$0 help\` for help."
1144;;
1145*)
1146break
1147;;
1148esac
1149shift
1150done
1151
1152# $1 is now a command name. Check if it is a valid command and, if so,
1153# run it.
1154#
1155declare -f "cmd_$1" > /dev/null
1156ok_or_die "Unknown command: $1. Please use \`$0 help\` for help."
1157
1158cmd=cmd_$1
1159shift
1160
1161# $@ is now a list of command-specific args
1162#
1163$cmd "$@"
1164}
1165
1166main "$@"
1167