1#!/bin/bash 2 3# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4# Copyright © 2020 Intel Corporation 5# SPDX-License-Identifier: Apache-2.0 6 7CLI_NAME="Cloud Hypervisor" 8 9CTR_IMAGE_TAG="cloudhypervisor/dev" 10CTR_IMAGE_VERSION="latest" 11CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}" 12 13DOCKER_RUNTIME="docker" 14 15# Host paths 16CLH_SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd) 17CLH_ROOT_DIR=$(cd "${CLH_SCRIPTS_DIR}/.." && pwd) 18CLH_BUILD_DIR="${CLH_ROOT_DIR}/build" 19CLH_CARGO_TARGET="${CLH_BUILD_DIR}/cargo_target" 20CLH_DOCKERFILE="${CLH_SCRIPTS_DIR}/../resources/Dockerfile" 21CLH_CTR_BUILD_DIR="/tmp/cloud-hypervisor/ctr-build" 22CLH_INTEGRATION_WORKLOADS="${HOME}/workloads" 23 24# Container paths 25CTR_CLH_ROOT_DIR="/cloud-hypervisor" 26CTR_CLH_CARGO_BUILT_DIR="${CTR_CLH_ROOT_DIR}/build" 27CTR_CLH_CARGO_TARGET="${CTR_CLH_CARGO_BUILT_DIR}/cargo_target" 28CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads" 29 30# Container networking option 31CTR_CLH_NET="host" 32[ $(uname -m) = "aarch64" ] && CTR_CLH_NET="bridge" 33 34# Cargo paths 35# Full path to the cargo registry dir on the host. This appears on the host 36# because we want to persist the cargo registry across container invocations. 37# Otherwise, any rust crates from crates.io would be downloaded again each time 38# we build or test. 39CARGO_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_registry" 40 41# Full path to the cargo git registry on the host. This serves the same purpose 42# as CARGO_REGISTRY_DIR, for crates downloaded from GitHub repos instead of 43# crates.io. 44CARGO_GIT_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_git_registry" 45 46# Full path to the cargo target dir on the host. 47CARGO_TARGET_DIR="${CLH_BUILD_DIR}/cargo_target" 48 49# Send a decorated message to stdout, followed by a new line 50# 51say() { 52 [ -t 1 ] && [ -n "$TERM" ] \ 53 && echo "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \ 54 || echo "[$CLI_NAME] $*" 55} 56 57# Send a decorated message to stdout, without a trailing new line 58# 59say_noln() { 60 [ -t 1 ] && [ -n "$TERM" ] \ 61 && echo -n "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \ 62 || echo "[$CLI_NAME] $*" 63} 64 65# Send a text message to stderr 66# 67say_err() { 68 [ -t 2 ] && [ -n "$TERM" ] \ 69 && echo "$(tput setaf 1)[$CLI_NAME] $*$(tput sgr0)" 1>&2 \ 70 || echo "[$CLI_NAME] $*" 1>&2 71} 72 73# Send a warning-highlighted text to stdout 74say_warn() { 75 [ -t 1 ] && [ -n "$TERM" ] \ 76 && echo "$(tput setaf 3)[$CLI_NAME] $*$(tput sgr0)" \ 77 || echo "[$CLI_NAME] $*" 78} 79 80# Exit with an error message and (optional) code 81# Usage: die [-c <error code>] <error message> 82# 83die() { 84 code=1 85 [[ "$1" = "-c" ]] && { 86 code="$2" 87 shift 2 88 } 89 say_err "$@" 90 exit $code 91} 92 93# Exit with an error message if the last exit code is not 0 94# 95ok_or_die() { 96 code=$? 97 [[ $code -eq 0 ]] || die -c $code "$@" 98} 99 100# Make sure the build/ dirs are available. Exit if we can't create them. 101# Upon returning from this call, the caller can be certain the build/ dirs exist. 102# 103ensure_build_dir() { 104 for dir in "$CLH_BUILD_DIR" \ 105 "$CLH_INTEGRATION_WORKLOADS" \ 106 "$CLH_CTR_BUILD_DIR" \ 107 "$CARGO_TARGET_DIR" \ 108 "$CARGO_REGISTRY_DIR" \ 109 "$CARGO_GIT_REGISTRY_DIR"; do 110 mkdir -p "$dir" || die "Error: cannot create dir $dir" 111 [ -x "$dir" ] && [ -w "$dir" ] || \ 112 { 113 say "Wrong permissions for $dir. Attempting to fix them ..." 114 chmod +x+w "$dir" 115 } || \ 116 die "Error: wrong permissions for $dir. Should be +x+w" 117 done 118} 119 120# Make sure we're using the latest dev container, by just pulling it. 121ensure_latest_ctr() { 122 $DOCKER_RUNTIME pull "$CTR_IMAGE" 123 124 ok_or_die "Error pulling container image. Aborting." 125} 126 127# Fix main directory permissions after a container ran as root. 128# Since the container ran as root, any files it creates will be owned by root. 129# This fixes that by recursively changing the ownership of /cloud-hypervisor to the 130# current user. 131# 132fix_dir_perms() { 133 # Yes, running Docker to get elevated privileges, just to chown some files 134 # is a dirty hack. 135 $DOCKER_RUNTIME run \ 136 --workdir "$CTR_CLH_ROOT_DIR" \ 137 --rm \ 138 --volume /dev:/dev \ 139 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 140 "$CTR_IMAGE" \ 141 chown -R "$(id -u):$(id -g)" "$CTR_CLH_ROOT_DIR" 142 143 return $1 144} 145 146cmd_help() { 147 echo "" 148 echo "Cloud Hypervisor $(basename $0)" 149 echo "Usage: $(basename $0) <command> [<command args>]" 150 echo "" 151 echo "Available commands:" 152 echo "" 153 echo " build [--debug|--release] [--libc musl|gnu] [-- [<cargo args>]]" 154 echo " Build the Cloud Hypervisor binaries." 155 echo " --debug Build the debug binaries. This is the default." 156 echo " --release Build the release binaries." 157 echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu" 158 echo "" 159 echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<cargo test args>]]" 160 echo " Run the Cloud Hypervisor tests." 161 echo " --unit Run the unit tests." 162 echo " --cargo Run the cargo tests." 163 echo " --integration Run the integration tests." 164 echo " --integration-sgx Run the SGX integration tests." 165 echo " --integration-windows Run the Windows guest integration tests." 166 echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu" 167 echo " --all Run all tests." 168 echo "" 169 echo " build-container [--type]" 170 echo " Build the Cloud Hypervisor container." 171 echo " --dev Build dev container. This is the default." 172 echo "" 173 echo " clean [<cargo args>]]" 174 echo " Remove the Cloud Hypervisor artifacts." 175 echo "" 176 echo " shell" 177 echo " Run the development container into an interactive, privileged BASH shell." 178 echo "" 179 echo " help" 180 echo " Display this help message." 181 echo "" 182} 183 184cmd_build() { 185 build="debug" 186 libc="gnu" 187 188 while [ $# -gt 0 ]; do 189 case "$1" in 190 "-h"|"--help") { cmd_help; exit 1; } ;; 191 "--debug") { build="debug"; } ;; 192 "--release") { build="release"; } ;; 193 "--libc") 194 shift 195 [[ "$1" =~ ^(musl|gnu)$ ]] || \ 196 die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"." 197 libc="$1" 198 ;; 199 "--") { shift; break; } ;; 200 *) 201 die "Unknown build argument: $1. Please use --help for help." 202 ;; 203 esac 204 shift 205 done 206 207 target="$(uname -m)-unknown-linux-${libc}" 208 209 cargo_args=("$@") 210 [ $build = "release" ] && cargo_args+=("--release") 211 cargo_args+=(--target "$target") 212 [ $(uname -m) = "aarch64" ] && cargo_args+=("--no-default-features") 213 [ $(uname -m) = "aarch64" ] && cargo_args+=(--features "kvm") 214 215 rustflags="" 216 if [ $(uname -m) = "aarch64" ] && [ $libc = "musl" ] ; then 217 rustflags="-C link-arg=-lgcc -C link_arg=-specs -C link_arg=/usr/lib/aarch64-linux-musl/musl-gcc.specs" 218 fi 219 220 $DOCKER_RUNTIME run \ 221 --user "$(id -u):$(id -g)" \ 222 --workdir "$CTR_CLH_ROOT_DIR" \ 223 --rm \ 224 --volume /dev:/dev \ 225 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 226 --env RUSTFLAGS="$rustflags" \ 227 "$CTR_IMAGE" \ 228 cargo build --all \ 229 --target-dir "$CTR_CLH_CARGO_TARGET" \ 230 "${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build" 231} 232 233cmd_clean() { 234 cargo_args=("$@") 235 236 $DOCKER_RUNTIME run \ 237 --user "$(id -u):$(id -g)" \ 238 --workdir "$CTR_CLH_ROOT_DIR" \ 239 --rm \ 240 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 241 "$CTR_IMAGE" \ 242 cargo clean \ 243 --target-dir "$CTR_CLH_CARGO_TARGET" \ 244 "${cargo_args[@]}" 245 } 246 247cmd_tests() { 248 unit=false 249 cargo=false 250 integration=false 251 integration_sgx=false 252 integration_windows=false 253 libc="gnu" 254 255 while [ $# -gt 0 ]; do 256 case "$1" in 257 "-h"|"--help") { cmd_help; exit 1; } ;; 258 "--unit") { unit=true; } ;; 259 "--cargo") { cargo=true; } ;; 260 "--integration") { integration=true; } ;; 261 "--integration-sgx") { integration_sgx=true; } ;; 262 "--integration-windows") { integration_windows=true; } ;; 263 "--libc") 264 shift 265 [[ "$1" =~ ^(musl|gnu)$ ]] || \ 266 die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"." 267 libc="$1" 268 ;; 269 "--all") { cargo=true; unit=true; integration=true; } ;; 270 "--") { shift; break; } ;; 271 *) 272 die "Unknown tests argument: $1. Please use --help for help." 273 ;; 274 esac 275 shift 276 done 277 278 target="$(uname -m)-unknown-linux-${libc}" 279 cflags="" 280 target_cc="" 281 if [[ "$target" == "x86_64-unknown-linux-musl" ]]; then 282 target_cc="musl-gcc" 283 cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/" 284 fi 285 286 if [ "$unit" = true ] ; then 287 say "Running unit tests for $target..." 288 $DOCKER_RUNTIME run \ 289 --workdir "$CTR_CLH_ROOT_DIR" \ 290 --rm \ 291 --device /dev/kvm \ 292 --device /dev/net/tun \ 293 --cap-add net_admin \ 294 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 295 --env BUILD_TARGET="$target" \ 296 --env CFLAGS="$cflags" \ 297 --env TARGET_CC="$target_cc" \ 298 "$CTR_IMAGE" \ 299 ./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $? 300 fi 301 302 if [ "$cargo" = true ] ; then 303 say "Running cargo tests..." 304 $DOCKER_RUNTIME run \ 305 --workdir "$CTR_CLH_ROOT_DIR" \ 306 --rm \ 307 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 308 "$CTR_IMAGE" \ 309 ./scripts/run_cargo_tests.sh || fix_dir_perms $? || exit $? 310 fi 311 312 if [ "$integration" = true ] ; then 313 say "Running integration tests for $target..." 314 $DOCKER_RUNTIME run \ 315 --workdir "$CTR_CLH_ROOT_DIR" \ 316 --rm \ 317 --privileged \ 318 --security-opt seccomp=unconfined \ 319 --ipc=host \ 320 --net="$CTR_CLH_NET" \ 321 --mount type=tmpfs,destination=/tmp \ 322 --volume /dev:/dev \ 323 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 324 --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ 325 --env USER="root" \ 326 --env CH_LIBC="${libc}" \ 327 "$CTR_IMAGE" \ 328 ./scripts/run_integration_tests_$(uname -m).sh "$@" || fix_dir_perms $? || exit $? 329 fi 330 331 if [ "$integration_sgx" = true ] ; then 332 say "Running SGX integration tests for $target..." 333 $DOCKER_RUNTIME run \ 334 --workdir "$CTR_CLH_ROOT_DIR" \ 335 --rm \ 336 --privileged \ 337 --security-opt seccomp=unconfined \ 338 --ipc=host \ 339 --net="$CTR_CLH_NET" \ 340 --mount type=tmpfs,destination=/tmp \ 341 --volume /dev:/dev \ 342 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 343 --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ 344 --env USER="root" \ 345 --env CH_LIBC="${libc}" \ 346 "$CTR_IMAGE" \ 347 ./scripts/run_integration_tests_sgx.sh "$@" || fix_dir_perms $? || exit $? 348 fi 349 350 if [ "$integration_windows" = true ] ; then 351 say "Running Windows integration tests for $target..." 352 $DOCKER_RUNTIME run \ 353 --workdir "$CTR_CLH_ROOT_DIR" \ 354 --rm \ 355 --privileged \ 356 --security-opt seccomp=unconfined \ 357 --ipc=host \ 358 --net="$CTR_CLH_NET" \ 359 --mount type=tmpfs,destination=/tmp \ 360 --volume /dev:/dev \ 361 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 362 --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ 363 --env USER="root" \ 364 --env CH_LIBC="${libc}" \ 365 "$CTR_IMAGE" \ 366 ./scripts/run_integration_tests_windows.sh "$@" || fix_dir_perms $? || exit $? 367 fi 368 fix_dir_perms $? 369} 370 371cmd_build-container() { 372 container_type="dev" 373 374 while [ $# -gt 0 ]; do 375 case "$1" in 376 "-h"|"--help") { cmd_help; exit 1; } ;; 377 "--dev") { container_type="dev"; } ;; 378 "--") { shift; break; } ;; 379 *) 380 die "Unknown build-container argument: $1. Please use --help for help." 381 ;; 382 esac 383 shift 384 done 385 386 BUILD_DIR=/tmp/cloud-hypervisor/container/ 387 388 mkdir -p $BUILD_DIR 389 cp $CLH_DOCKERFILE $BUILD_DIR 390 391 [ $(uname -m) = "aarch64" ] && TARGETARCH="arm64" 392 [ $(uname -m) = "x86_64" ] && TARGETARCH="amd64" 393 394 $DOCKER_RUNTIME build \ 395 --target $container_type \ 396 -t $CTR_IMAGE \ 397 -f $BUILD_DIR/Dockerfile \ 398 --build-arg TARGETARCH=$TARGETARCH \ 399 $BUILD_DIR 400} 401 402cmd_shell() { 403 say_warn "Starting a privileged shell prompt as root ..." 404 say_warn "WARNING: Your $CLH_ROOT_DIR folder will be bind-mounted in the container under $CTR_CLH_ROOT_DIR" 405 $DOCKER_RUNTIME run \ 406 -ti \ 407 --workdir "$CTR_CLH_ROOT_DIR" \ 408 --rm \ 409 --privileged \ 410 --security-opt seccomp=unconfined \ 411 --ipc=host \ 412 --net="$CTR_CLH_NET" \ 413 --tmpfs /tmp:exec \ 414 --volume /dev:/dev \ 415 --volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \ 416 --volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \ 417 --env USER="root" \ 418 --entrypoint bash \ 419 "$CTR_IMAGE" 420 421 fix_dir_perms $? 422} 423 424# Parse main command line args. 425# 426while [ $# -gt 0 ]; do 427 case "$1" in 428 -h|--help) { cmd_help; exit 1; } ;; 429 -y|--unattended) { OPT_UNATTENDED=true; } ;; 430 -*) 431 die "Unknown arg: $1. Please use \`$0 help\` for help." 432 ;; 433 *) 434 break 435 ;; 436 esac 437 shift 438done 439 440# $1 is now a command name. Check if it is a valid command and, if so, 441# run it. 442# 443declare -f "cmd_$1" > /dev/null 444ok_or_die "Unknown command: $1. Please use \`$0 help\` for help." 445 446cmd=cmd_$1 447shift 448 449ensure_build_dir 450if [ $(uname -m) = "x86_64" ]; then 451 ensure_latest_ctr 452fi 453 454$cmd "$@" 455