firecracker

Форк
0
/
devtool 
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)
72
DEVCTR_IMAGE_NO_TAG="public.ecr.aws/firecracker/fcuvm"
73

74
# Development container tag
75
DEVCTR_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.)
80
DEVCTR_IMAGE="${DEVCTR_IMAGE_NO_TAG}:${DEVCTR_IMAGE_TAG}"
81

82
# Full path to the Firecracker tools dir on the host.
83
FC_TOOLS_DIR=$(cd "$(dirname "$0")" && pwd)
84
source "$FC_TOOLS_DIR/functions"
85

86
# Full path to the Firecracker sources dir on the host.
87
FC_ROOT_DIR=$(cd "${FC_TOOLS_DIR}/.." && pwd)
88

89
# Full path to the build dir on the host.
90
FC_BUILD_DIR="${FC_ROOT_DIR}/build"
91

92
# Full path to devctr dir on the host.
93
FC_DEVCTR_DIR="${FC_ROOT_DIR}/tools/devctr"
94

95
# Path to the linux kernel directory on the host.
96
KERNEL_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.
102
CARGO_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.
107
CARGO_GIT_REGISTRY_DIR="${FC_BUILD_DIR}/cargo_git_registry"
108

109
# Full path to the cargo target dir on the host.
110
CARGO_TARGET_DIR="${FC_BUILD_DIR}/cargo_target"
111

112
# Full path to the Firecracker sources dir, as bind-mounted in the container.
113
CTR_FC_ROOT_DIR="/firecracker"
114

115
# Full path to the build dir, as bind-mounted in the container.
116
CTR_FC_BUILD_DIR="${CTR_FC_ROOT_DIR}/build"
117

118
# Full path to the cargo target dir, as bind-mounted in the container.
119
CTR_CARGO_TARGET_DIR="$CTR_FC_BUILD_DIR/cargo_target"
120

121
# Path to the microVM images cache dir
122
MICROVM_IMAGES_DIR="build/img"
123

124
# Full path to the public key mapping on the guest
125
PUB_KEY_PATH=/root/.ssh/id_rsa.pub
126

127
# Full path to the private key mapping on the guest
128
PRIV_KEY_PATH=/root/.ssh/id_rsa
129

130
# Path to the linux kernel directory, as bind-mounted in the container.
131
CTR_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
135
OPT_UNATTENDED=false
136

137
# Get the target prefix to avoid repeated calls to uname -m
138
TARGET_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
#
144
ensure_docker() {
145
    NEWLINE=$'\n'
146
    output=$(which docker 2>&1)
147
    ok_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

152
    output=$(docker ps 2>&1)
153
    ok_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
166
retry_cmd() {
167
    command=$1
168
    retry_cnt=$2
169
    sleep_int=$3
170

171
    {
172
        $command
173
    } || {
174
        # Command failed, substract one from retry_cnt
175
        retry_cnt=$((retry_cnt - 1))
176

177
        # If retry_cnt is larger than 0, sleep and call again
178
        if [ "$retry_cnt" -gt 0 ]; then
179
            echo "$command failed, retrying..."
180
            sleep "$sleep_int"
181
            retry_cmd "$command" "$retry_cnt" "$sleep_int"
182
        fi
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
#
190
ensure_devctr() {
191

192
    # We depend on having Docker present.
193
    ensure_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 ]] || {
198
        say "About to pull docker image $DEVCTR_IMAGE"
199
        get_user_confirmation || die "Aborted."
200

201
        # Run docker pull 5 times in case it fails - sleep 3 seconds
202
        # between attempts
203
        retry_cmd "docker pull $DEVCTR_IMAGE" 5 3
204

205
        ok_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
#
212
ensure_build_dir() {
213
    for dir in "$FC_BUILD_DIR" "$CARGO_TARGET_DIR" \
214
               "$CARGO_REGISTRY_DIR" "$CARGO_GIT_REGISTRY_DIR"; do
215
        create_dir "$dir"
216
    done
217
}
218

219
build_bin_path() {
220
    target="$1"
221
    profile="$2"
222
    binary="$3"
223
    echo "$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
#
231
cmd_fix_perms() {
232
    # Yes, running Docker to get elevated privileges, just to chown some files
233
    # is a dirty hack.
234
    run_devctr \
235
        -- \
236
        chown -R "$(id -u):$(id -g)" "$CTR_FC_BUILD_DIR"
237
}
238

239
# Builds the development container from its Dockerfile.
240
#
241
cmd_build_devctr() {
242
    docker_file_name=$FC_DEVCTR_DIR/Dockerfile
243
    build_args="--build-arg ARCH=$(uname -m)"
244

245
    while [ $# -gt 0 ]; do
246
        case "$1" in
247
            "-h"|"--help")      { cmd_help; exit 1; } ;;
248
            "--")               { shift; break;     } ;;
249
            *)
250
                die "Unknown argument: $1. Please use --help for help."
251
            ;;
252
        esac
253
        shift
254
    done
255

256
    docker 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.
262
validate_kernel_version() {
263
    local version_regex="^([0-9]+.)[0-9]+(.[0-9]+)?$"
264
    version="$1"
265

266
    if [ -z "$version" ]; then
267
        die "Kernel version cannot be empty."
268
    elif [[ ! "$version" =~ $version_regex ]]; then
269
        die "Invalid version number: $version (expected: \$Major.\$Minor.\$Patch(optional))."
270
    fi
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'"
278
run_devctr() {
279
    docker_args=()
280
    ctr_args=()
281
    docker_args_done=false
282
    while [[ $# -gt 0 ]]; do
283
        [[ "$1" = "--" ]] && {
284
            docker_args_done=true
285
            shift
286
            continue
287
        }
288
        [[ $docker_args_done = true ]] && ctr_args+=("$1") || docker_args+=("$1")
289
        shift
290
    done
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
298
    proxies=(http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY)
299
    for i in "${proxies[@]}"; do
300
        if [[ ! -z ${!i} ]]; then
301
            docker_args+=("--env") && docker_args+=("$i=${!i}")
302
        fi
303
    done
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.
308
    docker 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
#
320
test_key() {
321
    ssh-keygen -lf "$1" &>/dev/null
322
    ret=$?
323
    [ $ret -ne 0 ] && die "$1 is not a valid key file."
324
}
325

326
create_dir() {
327
    # Create a dir for the provided path.
328
    dir="$1"
329
    mkdir -p "$dir" || die "Error: cannot create dir $dir"
330
        [ -x "$dir" ] && [ -w "$dir" ] || \
331
            {
332
                say "Wrong permissions for $dir. Attempting to fix them ..."
333
                chmod +x+w "$dir"
334
            } || \
335
            die "Error: wrong permissions for $dir. Should be +x+w"
336
}
337

338
# `$0 help`
339
# Show the detailed devtool usage information.
340
#
341
cmd_help() {
342
    echo ""
343
    echo "Firecracker $(basename $0)"
344
    echo "Usage: $(basename $0) [<args>] <command> [<command args>]"
345
    echo ""
346
    echo "Global arguments"
347
    echo "    -y, --unattended         Run unattended. Assume the user would always"
348
    echo "                             answer \"yes\" to any confirmation prompt."
349
    echo ""
350
    echo "Available commands:"
351
    echo ""
352
    echo "    build [--debug|--release] [-l|--libc musl|gnu] [-- [<cargo args>]]"
353
    echo "        Build the Firecracker binaries."
354
    echo "        Firecracker is built using the Rust build system (cargo). All arguments after --"
355
    echo "        will be passed through to cargo."
356
    echo "        --debug               Build the debug binaries. This is the default."
357
    echo "        --release             Build the release binaries."
358
    echo "        -l, --libc musl|gnu   Choose the libc flavor against which Firecracker will"
359
    echo "                              be linked. Default is musl."
360
    echo "        --ssh-keys            Provide the paths to the public and private SSH keys on the host"
361
    echo "                              (in this particular order) required for the git authentication."
362
    echo "                              It is mandatory that both keys are specified."
363
    echo ""
364
    echo "    build_devctr"
365
    echo "        Builds the development container from its Dockerfile."
366
    echo ""
367
    echo "    checkenv"
368
    echo "        Performs prerequisites checks needed to execute firecracker."
369
    echo ""
370
    echo "    distclean"
371
    echo "        Clean up the build tree and remove the docker container."
372
    echo ""
373
    echo "    fix_perms"
374
    echo "        Fixes permissions when devtool dies in the middle of a privileged session."
375
    echo ""
376
    echo "    fmt"
377
    echo "        Auto-format all Rust source files, to match the Firecracker requirements."
378
    echo "        This should be used as the last step in every commit, to ensure that the"
379
    echo "        Rust style tests pass."
380
    echo ""
381
    echo "    generate_syscall_tables <version>"
382
    echo "        Generates the syscall tables for seccompiler, according to a given kernel version."
383
    echo "        Release candidate (rc) linux versions are not allowed."
384
    echo "        Outputs a rust file for each supported arch: src/seccompiler/src/syscall_table/{arch}.rs"
385
    echo "        Supported architectures: x86_64 and aarch64."
386
    echo ""
387
    echo "    install [-p|--path] [--debug|--release]"
388
    echo "      Install firecracker, jailer and seccomp binaries to /usr/local/bin or a given path."
389
    echo "      Only the musl linked binaries are supported."
390
    echo "        --path                Install binaries to a specified path."
391
    echo "        --debug               Install the debug binaries."
392
    echo "        --release             Install the release binaries. This is the default."
393
    echo ""
394
    echo "    help"
395
    echo "        Display this help message."
396
    echo ""
397
    echo "    shell [--privileged]"
398
    echo "        Launch the development container and open an interactive BASH shell."
399
    echo "        -p, --privileged    Run the container as root, in privileged mode."
400
    echo "                            Running Firecracker via the jailer requires elevated"
401
    echo "                            privileges, though the build phase does not."
402
    echo ""
403
    echo "    sh CMD..."
404
    echo "        Launch the development container and run a command."
405
    echo ""
406
    echo "    test [-- [<pytest args>]]"
407
    echo "        Run the Firecracker integration tests."
408
    echo "        The Firecracker testing system is based on pytest. All arguments after --"
409
    echo "        will be passed through to pytest."
410
    echo "        -c, --cpuset-cpus cpulist    Set a dedicated cpulist to be used by the tests."
411
    echo "        -m, --cpuset-mems memlist    Set a dedicated memlist to be used by the tests."
412
    echo "            --performance            Tweak various setting of the host running the tests (such as C- and P-states)"
413
    echo "                                     to achieve consistent performance. Used for running performance tests in CI."
414
    echo ""
415

416
    cat <<EOF
417
    test_debug [-- [<pytest args>]]
418
        Run tests in a debugging environment
419

420
    test_sandbox
421
        Run Firecracker in an IPython REPL
422

423
    mkdocs
424
        Use 'cargo doc' to generate rustdoc documentation
425

426
    checkstyle
427
        Run style checks
428
EOF
429
}
430

431

432
# `$0 build` - build Firecracker
433
# Please see `$0 help` for more information.
434
#
435
cmd_build() {
436
    # By default, we'll build the debug binaries.
437
    profile="debug"
438
    libc="musl"
439

440
    # Parse any command line args.
441
    while [ $# -gt 0 ]; do
442
        case "$1" in
443
            "-h"|"--help")  { cmd_help; exit 1;     } ;;
444
            "--debug")      { profile="debug";      } ;;
445
            "--release")    { profile="release";    } ;;
446
            "--ssh-keys")
447
                shift
448
                [[ -z "$1" ]] && \
449
                    die "Please provide the path to the public SSH key."
450
                [[ ! -f "$1" ]]  && die "The public key file does not exist: $1."
451
                test_key "$1"
452
                host_pub_key_path="$1"
453
                shift
454
                [[ -z "$1" ]] && \
455
                    die "Please provide the path to the private SSH key."
456
                [[ ! -f "$1" ]]  && die "The private key file does not exist: $1."
457
                test_key "$1"
458
                host_priv_key_path="$1"
459
                ;;
460
            "-l"|"--libc")
461
                shift
462
                [[ "$1" =~ ^(musl|gnu)$ ]] || \
463
                    die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
464
                libc="$1"
465
                ;;
466
            "--")           { shift; break;         } ;;
467
            *)
468
                die "Unknown argument: $1. Please use --help for help."
469
            ;;
470
        esac
471
        shift
472
    done
473

474
    # Check prerequisites
475
    ensure_devctr
476
    ensure_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" ] &&
480
        extra_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.
486
    run_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
492
    ret=$?
493
    return $ret
494
}
495

496
function cmd_make_release {
497
    run_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

504
cmd_distclean() {
505
    # List of folders to remove.
506
    dirs=("build" "test_results")
507

508
    for dir in "${dirs[@]}"; do
509
        if [ -d "$dir" ]; then
510
            say "Removing $dir"
511
            rm -rf "$dir"
512
        fi
513
    done
514

515
    # Remove devctr if it exists
516
    if [ $(docker images -q "$DEVCTR_IMAGE" | wc -l) -eq "1" ]; then
517
        say "Removing $DEVCTR_IMAGE"
518
        docker rmi -f "$DEVCTR_IMAGE"
519
    fi
520
}
521

522
ensure_ci_artifacts() {
523
    if ! command -v aws >/dev/null; then
524
      die "AWS CLI not installed, which is required for downloading artifacts for integration tests."
525
    fi
526

527
    # Fetch all the artifacts so they are local
528
    say "Fetching CI artifacts from S3"
529
    S3_URL=s3://spec.ccfc.min/firecracker-ci/v1.7/$(uname -m)
530
    ARTIFACTS=$MICROVM_IMAGES_DIR/$(uname -m)
531
    if [ ! -d "$ARTIFACTS" ]; then
532
        mkdir -pv $ARTIFACTS
533
        aws s3 sync --no-sign-request "$S3_URL" "$ARTIFACTS"
534
        # fix permissions
535
        find "$ARTIFACTS" -type f -name "*.id_rsa" |xargs chmod -c 400
536
        find "$ARTIFACTS/firecracker" -type f |xargs chmod -c 755
537
    fi
538
}
539

540
apply_linux_61_tweaks() {
541
    KV=$(uname -r)
542
    if [[ $KV != 6.1.* ]] || [ $(uname -m) != x86_64 ]; then
543
        return
544
    fi
545
    say "Applying Linux 6.1 boot-time regression mitigations"
546

547
    KVM_VENDOR_MOD=$(lsmod |grep -P "^kvm_(amd|intel)" | awk '{print $1}')
548
    ITLB_MULTIHIT=/sys/devices/system/cpu/vulnerabilities/itlb_multihit
549
    NX_HUGEPAGES=/sys/module/kvm/parameters/nx_huge_pages
550

551
    # If m6a/m6i
552
    if grep -q "Not affected" $ITLB_MULTIHIT; then
553
        echo -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.
556
        lockfile="/tmp/.linux61_tweaks.lock"
557
        set -C # noclobber
558
        while true; do
559
            if echo "$$" > "$lockfile"; then
560
                echo "Successfully acquired lock"
561
                if ! grep -q "never" $NX_HUGEPAGES; then
562
                    echo "Reloading KVM modules with nx_huge_pages=never"
563
                    sudo modprobe -r $KVM_VENDOR_MOD kvm
564
                    sudo modprobe kvm nx_huge_pages=never
565
                    sudo modprobe $KVM_VENDOR_MOD
566
                fi
567
                rm "$lockfile"
568
                break
569
            else
570
                sleep 5s
571
            fi
572
        done
573
        tail -v $ITLB_MULTIHIT $NX_HUGEPAGES
574
    # else (m5d Skylake and CascadeLake)
575
    else
576
        echo "CPU vulnerable to iTLB_multihit, checking if favordynmods is enabled"
577
        mount |grep cgroup |grep -q favordynmods
578
        if [ $? -ne 0 ]; then
579
            say_warn "cgroups' favordynmods option not enabled; VM creation performance may be impacted"
580
        else
581
            echo "favordynmods is enabled"
582
        fi
583
    fi
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
593
apply_performance_tweaks() {
594
  # m6a instances do not support the amd_pstate driver (yet), so nothing we can do there
595
  if [[ -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
598
    echo 1 |sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo &> /dev/null
599

600
    # Save old values to restore later
601
    MIN_PERF_PCT=$(cat /sys/devices/system/cpu/intel_pstate/min_perf_pct)
602
    MAX_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
607
    echo 100 |sudo tee /sys/devices/system/cpu/intel_pstate/min_perf_pct &> /dev/null
608
    echo 100 |sudo tee /sys/devices/system/cpu/intel_pstate/max_perf_pct &> /dev/null
609
  elif [[ -f /sys/devices/system/cpu/cpufreq/boost ]] && [ ! $(uname -r | grep 4.14) ]; then
610
    echo 0 |sudo tee /sys/devices/system/cpu/cpufreq/boost &> /dev/null
611
  fi
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
616
  echo 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).
625
  echo 1 |sudo tee /sys/devices/system/cpu/cpu*/cpuidle/state*/disable &> /dev/null
626
}
627

628
unapply_performance_tweaks() {
629
  if [[ -d /sys/devices/system/cpu/intel_pstate ]]; then
630
    # reenable turbo boost
631
    echo 0 |sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo &> /dev/null
632

633
    # restore p-state limits
634
    echo $MIN_PERF_PCT |sudo tee /sys/devices/system/cpu/intel_pstate/min_perf_pct &> /dev/null
635
    echo $MAX_PERF_PCT |sudo tee /sys/devices/system/cpu/intel_pstate/max_perf_pct &> /dev/null
636
  elif [[ -f /sys/devices/system/cpu/cpufreq/boost ]]; then
637
    echo 1 | sudo tee /sys/devices/system/cpu/cpufreq/boost &> /dev/null
638
  fi
639

640
  # reenable deeper sleep states
641
  echo 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
#
651
cmd_test() {
652
    do_ab_test=0
653
    # Parse any command line args.
654
    while [ $# -gt 0 ]; do
655
        case "$1" in
656
            "-h"|"--help")      { cmd_help; exit 1; } ;;
657
            "-c"|"--cpuset-cpus")
658
                shift
659
                local cpuset_cpus="$1"
660
                ;;
661
            "-m"|"--cpuset-mems")
662
                shift
663
                local cpuset_mems="$1"
664
                ;;
665
            "--performance")
666
                local performance_tweaks=1;
667
                ;;
668
            "--ab")
669
                do_ab_test=1
670
                ;;
671
            "--")               { shift; break;     } ;;
672
            *)
673
                die "Unknown argument: $1. Please use --help for help."
674
            ;;
675
        esac
676
        shift
677
    done
678

679
    # Check prerequisites.
680
    ensure_kvm
681
    ensure_devctr
682
    ensure_build_dir
683
    ensure_ci_artifacts
684

685
    apply_linux_61_tweaks
686

687
    # If we got to here, we've got all we need to continue.
688
    say "Kernel version: $(uname -r)"
689
    say "$(sed '/^processor.*: 0$/,/^processor.*: 1$/!d; /^processor.*: 1$/d' /proc/cpuinfo)"
690
    say "RPM microcode_ctl version: $(rpm -q microcode_ctl)"
691

692
    env |grep -P "^(AWS_EMF_|BUILDKITE|CODECOV_)" > env.list
693
    if [[ $performance_tweaks -eq 1 ]]; then
694
      if [[ "$(uname --machine)" == "x86_64" ]]; then
695
        say "Detected CI and performance tests, tuning CPU frequency scaling and idle states for reduced variability"
696

697
        apply_performance_tweaks
698
      fi
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.
704
      say "Setting up huge pages pool"
705
      num_hugetlbfs_pages=2048
706

707
      huge_pages_old=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)
708
      huge_pages_new=$(echo $num_hugetlbfs_pages |sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages)
709
    fi
710

711
    if [[ "$huge_pages_new" -ne "$num_hugetlbfs_pages" ]]; then
712
      die "Failed to allocate $num_hugetlbfs_pages hugetlbfs pages, only got $huge_pages_new"
713
    fi
714

715
    say "Starting test run ..."
716

717
    test_script="./tools/test.sh"
718

719
    if [ $do_ab_test -eq 1 ]; then
720
      test_script="./tools/ab_test.py"
721
    fi
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.
727
    run_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

740
    ret=$?
741

742
    say "Finished test run ..."
743

744
    # Running as root would have created some root-owned files under the build
745
    # dir. Let's fix that.
746
    cmd_fix_perms
747

748
    # undo performance tweaks (in case the instance gets recycled for a non-perf test)
749
    if [[ $performance_tweaks -eq 1 ]]; then
750
      if [[ "$(uname --machine)" == "x86_64" ]]; then
751
        unapply_performance_tweaks
752
      fi
753

754
      echo $huge_pages_old |sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages >/dev/null
755
    fi
756

757
    # do not leave behind env.list file
758
    rm env.list
759

760
    return $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
#
767
cmd_shell() {
768

769
    # By default, we run the container as the current user.
770
    privileged=false
771

772
    # Parse any command line args.
773
    while [ $# -gt 0 ]; do
774
        case "$1" in
775
            "-h"|"--help")          { cmd_help; exit 1; } ;;
776
            "-p"|"--privileged")    { privileged=true;  } ;;
777
            "--")               { shift; break;     } ;;
778
            *)
779
                die "Unknown argument: $1. Please use --help for help."
780
            ;;
781
        esac
782
        shift
783
    done
784

785
    # Make sure we have what we need to continue.
786
    ensure_devctr
787
    ensure_build_dir
788

789
    if [[ $privileged = true ]]; then
790
        # If requested, spin up a privileged container.
791
        #
792
        say "Dropping to a privileged shell prompt ..."
793
        say "Note: $FC_ROOT_DIR is bind-mounted under $CTR_FC_ROOT_DIR"
794
        say_warn "You are running as root; any files that get created under" \
795
            "$CTR_FC_ROOT_DIR will be owned by root."
796
        run_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
            -- \
803
            bash
804
        ret=$?
805

806
        # Running as root may have created some root-owned files under the build
807
        # dir. Let's fix that.
808
        #
809
        cmd_fix_perms
810
    else
811
        say "Dropping to shell prompt as user $(whoami) ..."
812
        say "Note: $FC_ROOT_DIR is bind-mounted under $CTR_FC_ROOT_DIR"
813
        say_warn "You won't be able to run Firecracker via the jailer," \
814
            "but you can still build it."
815
        say "You can use \`$0 shell --privileged\` to get a root shell."
816

817
        [ -w /dev/kvm ] || \
818
            say_warn "WARNING: user $(whoami) doesn't have permission to" \
819
                "access /dev/kvm. You won't be able to run Firecracker."
820

821
        run_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
            -- \
829
            bash --norc
830
        ret=$?
831
    fi
832

833
    return $ret
834
}
835

836
cmd_sh() {
837
    ensure_build_dir
838
    ensure_ci_artifacts
839
    run_devctr \
840
        --privileged \
841
        --ulimit nofile=4096:4096 \
842
        --ulimit memlock=-1:-1 \
843
        --workdir "$CTR_FC_ROOT_DIR" \
844
        -- \
845
        bash --norc -c "$*"
846
}
847

848
cmd_sandbox() {
849
    cmd_sh "tmux new env PYTEST_ADDOPTS=--pdbcls=IPython.terminal.debugger:TerminalPdb PYTHONPATH=tests IPYTHONDIR=\$PWD/.ipython ipython -i ./tools/sandbox.py $@"
850
}
851

852
cmd_test_debug() {
853
    cmd_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
#
860
cmd_fmt() {
861
    cmd_sh "cargo fmt --all -- --config $(tr '\n' ',' <tests/fmt.toml)"
862
}
863

864
cmd_mkdocs() {
865
    cmd_sh "cargo doc --workspace --no-deps --document-private-items"
866
}
867

868
cmd_checkstyle() {
869
    cmd_test -- integration_tests/style -n 4 --dist worksteal
870
}
871

872
# Check if able to run firecracker.
873
# ../docs/getting-started.md#prerequisites
874
ensure_kvm_rw () {
875
    [[ -c /dev/kvm && -w /dev/kvm && -r /dev/kvm ]] || \
876
        say_err "FAILED: user $(whoami) doesn't have permission to" \
877
                "access /dev/kvm."
878
}
879

880
check_kernver () {
881
    KERN_MAJOR=4
882
    KERN_MINOR=14
883
    (uname -r | awk -v MAJOR=$KERN_MAJOR -v MINOR=$KERN_MINOR '{ split($0,kver,".");
884
    if( (kver[1] + (kver[2] / 100) ) <  MAJOR + (MINOR/100) )
885
    {
886
      exit 1;
887
    } }') ||
888
    say_err "FAILED: Kernel version must be >= $KERN_MAJOR.$KERN_MINOR"
889
}
890

891
# Check Production Host Setup
892
# ../docs/prod-host-setup.md
893

894
check_KPTI () {
895
    (grep -q "^Mitigation: PTI$" \
896
      /sys/devices/system/cpu/vulnerabilities/meltdown) || \
897
    say_warn "WARNING: KPTI NOT SUPPORTED."
898
}
899

900
check_KSM () {
901
    (grep -q "^0$" /sys/kernel/mm/ksm/run) || \
902
    say_warn "WARNING: KSM ENABLED."
903
}
904

905
check_vulns () {
906
    for f in /sys/devices/system/cpu/vulnerabilities/* ; do
907
        if $(grep -q "Vulnerable" ${f}) ; then
908
            say_warn "WARNING: `basename $f`: VULNERABLE.";
909
        fi
910
    done
911
}
912

913
check_swap () {
914
    (grep -q "swap.img" /proc/swaps ) && \
915
    say_warn "WARNING: SWAP ENABLED."
916
}
917

918
check_EPT() {
919
    if [ "$(uname --machine)" = "x86_64" ]; then
920
        (grep -q "Y" /sys/module/kvm_intel/parameters/ept ; [ $? -ne 1 ]) || \
921
        say_warn "WARNING: EPT DISABLED. Performance will be affected."
922
    fi
923
}
924

925
check_vm() {
926
    if [ $(dmesg | grep -c -i "hypervisor detected") -gt 0 ]; then
927
        say_warn "WARNING: you are running in a virtual machine." \
928
    "Firecracker is not well tested under nested virtualization."
929
    fi
930
}
931

932
cmd_checkenv() {
933
    # Parse any command line args.
934
    while [ $# -gt 0 ]; do
935
        case "$1" in
936
            "-h"|"--help")      { cmd_help; exit 1; } ;;
937
            *)
938
                die "Unknown argument: $1. Please use --help for help."
939
        ;;
940
        esac
941
        shift
942
    done
943
    PROD_DOC="../docs/prod-host-setup.md"
944
    QUICKSTART="../docs/getting-started.md#prerequisites"
945
    say "Checking prerequisites for running Firecracker."
946
    say "Please check $QUICKSTART in case of any error."
947
    ensure_kvm_rw
948
    check_kernver
949
    check_vm
950
    say "Checking Host Security Configuration."
951
    say "Please check $PROD_DOC in case of any error."
952
    check_KSM
953
    check_swap
954
    check_EPT
955
    check_vulns
956
}
957

958
generate_syscall_table_x86_64() {
959
    path_to_rust_file="$FC_ROOT_DIR/src/seccompiler/src/syscall_table/x86_64.rs"
960

961
    echo "$header" > $path_to_rust_file
962

963
    # the table for x86_64 is nicely formatted here: linux/arch/x86/entry/syscalls/syscall_64.tbl
964
    cat linux/arch/x86/entry/syscalls/syscall_64.tbl | grep -v "^#" | grep -v -e '^$' |\
965
        awk '{print $2,$3,$1}' | grep -v "^x32" |\
966
        awk '{print "    map.insert(\""$2"\".to_string(), "$3");"}' | sort >> $path_to_rust_file
967

968
    echo "$footer" >> $path_to_rust_file
969

970
    say "Generated at: $path_to_rust_file"
971
}
972

973
generate_syscall_table_aarch64() {
974
    path_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
978
    replace+='s/__NR3264_fadvise64/223/;'
979
    replace+='s/__NR3264_fcntl/25/;'
980
    replace+='s/__NR3264_fstatat/79/;'
981
    replace+='s/__NR3264_fstatfs/44/;'
982
    replace+='s/__NR3264_fstat/80/;'
983
    replace+='s/__NR3264_ftruncate/46/;'
984
    replace+='s/__NR3264_lseek/62/;'
985
    replace+='s/__NR3264_sendfile/71/;'
986
    replace+='s/__NR3264_statfs/43/;'
987
    replace+='s/__NR3264_truncate/45/;'
988
    replace+='s/__NR3264_mmap/222/;'
989

990
    echo "$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.
995
    run_devctr \
996
        --user "$(id -u):$(id -g)" \
997
        --workdir "$CTR_KERNEL_DIR" \
998
        -- \
999
            gcc -Ilinux/include/uapi -E -dM -D__ARCH_WANT_RENAMEAT\
1000
                -D__BITS_PER_LONG=64\
1001
                linux/arch/arm64/include/uapi/asm/unistd.h |\
1002
                grep "#define __NR_" | grep -v "__NR_syscalls" |\
1003
                grep -v "__NR_arch_specific_syscall" |\
1004
                awk -F '__NR_' '{print $2}' |\
1005
                sed $replace |\
1006
                awk '{ print "    map.insert(\""$1"\".to_string(), "$2");" }' |\
1007
                sort -d >> $path_to_rust_file
1008
    ret=$?
1009

1010
    [ $ret -ne 0 ] && return $ret
1011

1012
    echo "$footer" >> $path_to_rust_file
1013

1014
    say "Generated at: $path_to_rust_file"
1015
}
1016

1017
cmd_generate_syscall_tables() {
1018
    # Parse any command line args.
1019
    while [ $# -gt 0 ]; do
1020
        case "$1" in
1021
            "-h"|"--help")      { cmd_help; exit 1;    } ;;
1022
            *)                  { kernel_version="$1"; break; } ;;
1023
        esac
1024
        shift
1025
    done
1026

1027
    validate_kernel_version "$kernel_version"
1028

1029
    kernel_major=v$(echo ${kernel_version} | cut -d . -f 1).x
1030
    kernel_baseurl=https://www.kernel.org/pub/linux/kernel/${kernel_major}
1031
    kernel_archive=linux-${kernel_version}.tar.xz
1032

1033
    ensure_devctr
1034

1035
    # Create the kernel clone directory
1036
    rm -rf "$KERNEL_DIR"
1037
    create_dir "$KERNEL_DIR"
1038
    cd "$KERNEL_DIR"
1039

1040
    say "Fetching linux kernel..."
1041

1042
    # Get sha256 checksum.
1043
    curl -fsSLO ${kernel_baseurl}/sha256sums.asc && \
1044
    kernel_sha256=$(grep ${kernel_archive} sha256sums.asc | cut -d ' ' -f 1)
1045
    # Get kernel archive.
1046
    curl -fsSLO "$kernel_baseurl/$kernel_archive" && \
1047
    # Verify checksum.
1048
    echo "${kernel_sha256}  ${kernel_archive}" | sha256sum -c - && \
1049
    # Decompress the kernel source.
1050
    xz -d "${kernel_archive}" && \
1051
    cat linux-${kernel_version}.tar | tar -x && mv linux-${kernel_version} linux
1052

1053
    ret=$?
1054
    [ $ret -ne 0 ] && return $ret
1055

1056
    # rust file header
1057
    read -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

1066
use std::collections::HashMap;
1067

1068
pub(crate) fn make_syscall_table(map: &mut HashMap<String, i64>) {
1069
EOM
1070

1071
    # rust file footer
1072
    read -r -d '' footer << EOM
1073
}
1074

1075
EOM
1076

1077
    # generate syscall table for x86_64
1078
    say "Generating table for x86_64..."
1079
    generate_syscall_table_x86_64 $header $footer
1080

1081
    # generate syscall table for aarch64
1082
    say "Generating table for aarch64..."
1083
    generate_syscall_table_aarch64 $header $footer
1084

1085
    ret=$?
1086
    [ $ret -ne 0 ] && return $ret
1087
}
1088

1089
cmd_install() {
1090
    # By default we install release/musl binaries.
1091
    profile="release"
1092
    target="$TARGET_PREFIX""musl"
1093
    install_path="/usr/local/bin"
1094
    binaries=("firecracker" "jailer" "seccompiler-bin" "rebase-snap" "cpu-template-helper")
1095

1096
    # Parse any command line args.
1097
    while [ $# -gt 0 ]; do
1098
        case "$1" in
1099
            "-h"|"--help") { cmd_help; exit 1; } ;;
1100
            "-p"|"--path")
1101
                shift;
1102
                install_path=$1;
1103
                ;;
1104
            "--debug")      { profile="debug";      } ;;
1105
            "--release")    { profile="release";    } ;;
1106
            *)
1107
                die "Unknown argument: $1. Please use --help for help."
1108
            ;;
1109
        esac
1110
        shift
1111
    done
1112

1113
    # Check that the binaries exist first
1114
    for binary in "${binaries[@]}"; do
1115
        bin_path=$( build_bin_path "$target" "$profile" "$binary" )
1116
        if [ ! -f "$bin_path" ]; then
1117
            die "Missing release binary. Needed file: $bin_path\n"\
1118
            "To build the binaries, run:\n\t$0 build --$profile"
1119
        fi
1120
    done
1121

1122
    # Install the binaries
1123
    for binary in "${binaries[@]}"; do
1124
        say "Installing $binary in $install_path"
1125
        install -m 755 "$( build_bin_path "$target" "$profile" "$binary" )" "$install_path"
1126
    done
1127
}
1128

1129

1130
main() {
1131

1132
    if [ $# = 0 ]; then
1133
    die "No command provided. Please use \`$0 help\` for help."
1134
    fi
1135

1136
    # Parse main command line args.
1137
    #
1138
    while [ $# -gt 0 ]; do
1139
        case "$1" in
1140
            -h|--help)              { cmd_help; exit 1;     } ;;
1141
            -y|--unattended)        { OPT_UNATTENDED=true;  } ;;
1142
            -*)
1143
                die "Unknown arg: $1. Please use \`$0 help\` for help."
1144
            ;;
1145
            *)
1146
                break
1147
            ;;
1148
        esac
1149
        shift
1150
    done
1151

1152
    # $1 is now a command name. Check if it is a valid command and, if so,
1153
    # run it.
1154
    #
1155
    declare -f "cmd_$1" > /dev/null
1156
    ok_or_die "Unknown command: $1. Please use \`$0 help\` for help."
1157

1158
    cmd=cmd_$1
1159
    shift
1160

1161
    # $@ is now a list of command-specific args
1162
    #
1163
    $cmd "$@"
1164
}
1165

1166
main "$@"
1167

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.