diff --git a/example.nix b/example.nix deleted file mode 100644 index 50dbc5c82..000000000 --- a/example.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ withCcache ? false, - - doCheck ? true, # boot unikernel after building it - includeos ? import ./default.nix { inherit withCcache; }, -}: - -includeos.stdenv.mkDerivation rec { - pname = "includeos_example"; - src = includeos.pkgs.lib.cleanSource ./example; - dontStrip = true; - inherit doCheck; - - nativeBuildInputs = [ - includeos.pkgs.buildPackages.nasm - includeos.pkgs.buildPackages.cmake - ]; - - buildInputs = [ - includeos - includeos.chainloader - ]; - - nativeCheckInputs = [ - includeos.vmrunner - includeos.pkgs.qemu - ]; - - checkPhase = '' - boot *.elf.bin - ''; - - version = "dev"; -} diff --git a/shell.nix b/shell.nix index b26dbef07..f7861a820 100644 --- a/shell.nix +++ b/shell.nix @@ -3,9 +3,6 @@ # nix-shell --argstr buildpath . buildpath ? "", - # The unikernel to build - unikernel ? "./example", - # vmrunner path, for vmrunner development vmrunner ? "", @@ -16,7 +13,6 @@ smp ? false, includeos ? import ./default.nix { inherit withCcache; inherit smp; } - }: includeos.pkgs.mkShell.override { inherit (includeos) stdenv; } rec { @@ -48,49 +44,44 @@ includeos.pkgs.mkShell.override { inherit (includeos) stdenv; } rec { ]; shellHook = '' + cat <<-EOF +================================== IncludeOS nix-shell ================================== +Packages: + IncludeOS: ${includeos} + vmrunner: ${vmrunnerPkg} + chainloader: ${includeos.chainloader} - unikernel=$(realpath ${unikernel}) - echo -e "Attempting to build unikernel: \n$unikernel" - if [ ! -d "$unikernel" ]; then - echo "$unikernel is not a valid directory" - exit 1 - fi - export BUILDPATH=${buildpath} - if [ -z "${buildpath}" ]; then - export BUILDPATH="$(mktemp -d)" - pushd "$BUILDPATH" - else - mkdir -p "$BUILDPATH" - pushd "$BUILDPATH" - fi - cmake "$unikernel" -DARCH=x86_64 -DINCLUDEOS_PACKAGE=${includeos} -DCMAKE_MODULE_PATH=${includeos}/cmake \ - -DFOR_PRODUCTION=OFF - make -j $NIX_BUILD_CORES - echo -e "\n====================== IncludeOS nix-shell =====================" - if [ -z "${buildpath}" ]; then - echo -e "\nWorking directory, generated by this script:" - echo $BUILDPATH - echo -e "\nTo use another directory pass in 'buildpath' to nix:" - echo "nix-shell --argstr buildpath you/build/path" - fi - echo -e "\nThe C++ compiler set to:" - echo $(which $CXX) - echo -e "\nIncludeOS package:" - echo ${includeos} - echo -e "\n---------------------- Network privileges ---------------------" - echo "The vmrunner for IncludeOS tests requires bridged networking for full functionality." - echo "The following commands requiring sudo privileges can be used to set this up:" - echo "1. the qemu-bridge-helper needs sudo to create a bridge. Can be enabled with:" - echo " sudo chmod u+s ${includeos.pkgs.qemu}/libexec/qemu-bridge-helper" - echo "2. bridge43 must exist. Can be set up with vmrunner's create_bridge.sh script:" - echo " ${vmrunnerPkg.create_bridge}" - echo "3. /etc/qemu/bridge.conf must contain this line:" - echo " allow bridge43" - echo "" - echo "Some tests require ping, which requires premissions to send raw packets. On some hosts" - echo "this is not enabled by default for iputils provided by nix. It can be enabled with:" - echo "4. sudo setcap cap_net_raw+ep ${includeos.pkgs.iputils}/bin/ping" - echo " " - echo +Tooling: + CXX $(command -v $CXX) + cmake: $(command -v cmake) + nasm: $(command -v nasm) + qemu-system-x86: $(command -v qemu-system-x86_64) + grub-mkrescue: $(command -v grub-mkrescue) + xorriso: $(command -v xorriso) + ping: $(command -v ping) + +---------------------------------- Network privileges ---------------------------------- +The vmrunner for IncludeOS tests requires bridged networking for full functionality. +The following checklist can be used to set this up from the host: + +1. The qemu-bridge-helper needs root escalation to manipulate bridges. You can provide this + either through capabilities or through root execution. Pick one: + sudo chmod u+s ${includeos.pkgs.qemu}/libexec/qemu-bridge-helper + sudo setcap cap_net_admin+ep ${includeos.pkgs.qemu}/libexec/qemu-bridge-helper + +2. bridge43 must exist. Can be set up with vmrunner's create_bridge.sh script (not as root): + ${vmrunnerPkg.create_bridge} + +3. /etc/qemu/bridge.conf must contain this line: + allow bridge43 + Also note that /etc/qemu needs specific permissions, so it might be easiest to install + qemu on the host to generate these directories for you, despite not using its executable here. + +4. Some tests also perform ICMP pings, which requires permissions to send raw packets. On some + hosts this is not enabled by default for iputils provided by nix. + It can be enabled with: + sudo setcap cap_net_raw+ep ${includeos.pkgs.iputils}/bin/ping + +EOF ''; } diff --git a/test.sh b/test.sh index 52e8275c0..671d1efd9 100755 --- a/test.sh +++ b/test.sh @@ -67,23 +67,23 @@ build_chainloader(){ } build_example(){ - nix-build $CCACHE_FLAG example.nix + nix-build $CCACHE_FLAG unikernel.nix } multicore_subset(){ - nix-shell --pure --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --run ./test.py + nix-build ./unikernel.nix --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --arg doCheck true # The following tests are not using multiple CPU's, but have been equippedd with some anyway # to make sure core functionality is not broken by missing locks etc. when waking up more cores. - nix-shell --pure --arg smp true $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --run ./test.py - nix-shell --pure --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --run ./test.py + nix-shell ./unikernel.nix --arg smp true $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --arg doCheck true + nix-build ./unikernel.nix --arg smp true $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --arg doCheck true } smoke_tests(){ - nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --run ./test.py - nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/net/integration/tcp --run ./test.py - nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --run ./test.py - nix-shell --pure $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --run ./test.py + nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/net/integration/udp --arg doCheck true + nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/net/integration/tcp --arg doCheck true + nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/paging --arg doCheck true + nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ./test/kernel/integration/smp --arg doCheck true } run unittests "Build and run unit tests" @@ -137,6 +137,7 @@ run_testsuite() { for subfolder in "$base_folder"/*/; do local skip=false + local sandboxed=true for exclude in "${exclusion_list[@]}"; do if [[ "$subfolder" == *"$exclude"* ]]; then @@ -144,14 +145,23 @@ run_testsuite() { break fi done - if [ "$skip" = true ]; then continue fi + for unsandbox in "${unsandbox_list[@]}"; do + if [[ "$subfolder" == *"$unsandbox"* ]]; then + sandboxed=false + break + fi + done # The command to run, as string to be able to print the fully expanded command - cmd="nix-shell --pure $CCACHE_FLAG --argstr unikernel $subfolder --run ./test.py" + if $sandboxed; then + cmd="nix-build ./unikernel.nix $CCACHE_FLAG --argstr unikernel ${subfolder%/} --arg doCheck true" + else + cmd="nix-shell ./unikernel.nix $CCACHE_FLAG --argstr unikernel ${subfolder%/} --arg doCheck true" + fi echo "" echo "🚧 Step $steps.$substeps" @@ -194,7 +204,11 @@ exclusions=( "modules" # Requires 32-bit build, which our shell.nix is not set up for ) +unsandbox_list=( + "term" # fails to create the tun device, like the net integration tests +) run_testsuite "./test/kernel/integration" "${exclusions[@]}" +unsandbox_list=() # # C++ STL runtime tests @@ -223,7 +237,20 @@ exclusions=( "websocket" # Linking fails, undefined ref to http_parser_parse_url, http_parser_execute ) +# all the following fail with the following error: +# failed to create tun device: Operation not permitted +# qemu-system-x86_64: -netdev bridge,id=net0,br=bridge43: bridge helper failed +unsandbox_list=( + "./test/net/integration/configure" + "./test/net/integration/icmp" + "./test/net/integration/icmp6" + "./test/net/integration/slaac" + "./test/net/integration/tcp" + "./test/net/integration/udp" + "./test/net/integration/dns" # except this one which times out instead +) run_testsuite "./test/net/integration" "${exclusions[@]}" +unsandbox_list=() echo -e "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" diff --git a/unikernel.nix b/unikernel.nix new file mode 100644 index 000000000..1aab8d755 --- /dev/null +++ b/unikernel.nix @@ -0,0 +1,125 @@ +{ + # The unikernel to build + unikernel ? ./example, + + # The test file to run + test ? "test.py", + + # Boot unikernel after building it + doCheck ? false, + + # Which architecture to build against + arch ? "x86_64", + + # Enable multicore suport. + smp ? false, + + # Enable ccache support. See overlay.nix for details. + withCcache ? false, + + # Enable stricter requirements + forProduction ? false, + + # The includeos library to build and link against + includeos ? import ./default.nix { inherit withCcache; inherit smp; }, + + # vmrunner path, for vmrunner development + vmrunner ? "", +}: +let + absolutePathOf = base: p: + if p == null then null else + if builtins.isPath p then p + else builtins.toPath (base + "/${p}"); + + unikernelPath = absolutePathOf ./. unikernel; + vmrunnerPkg = + if vmrunner == "" then + includeos.vmrunner + else + includeos.pkgs.callPackage (builtins.toPath /. + vmrunner) {}; +in +includeos.stdenv.mkDerivation rec { + pname = "includeos_example"; + version = "dev"; + src = includeos.pkgs.lib.cleanSource unikernelPath; + dontStrip = true; + inherit doCheck; + + nativeBuildInputs = [ + includeos.pkgs.buildPackages.nasm + includeos.pkgs.buildPackages.cmake + ]; + + buildInputs = [ + includeos + includeos.chainloader + ]; + + cmakeFlags = [ + "-DARCH=${arch}" + "-DINCLUDEOS_PACKAGE=${includeos}" + "-DCMAKE_MODULE_PATH=${includeos}/cmake" + "-DFOR_PRODUCTION=${if forProduction then "ON" else "OFF"}" + ]; + + installPhase = '' + runHook preInstall + # we want to place any files required by the test into the output + find -mindepth 1 -maxdepth 1 -type f -exec install -v -D -t "$out/" {} \; + + # especially the unikernel image, in case it wasn't at the rootdir already + find -mindepth 2 -name '*.elf.bin' -exec install -v -t "$out/" {} \; + runHook postInstall + ''; + + + nativeCheckInputs = [ + includeos.vmrunner + includeos.pkgs.grub2 + includeos.pkgs.python3 + includeos.pkgs.qemu + includeos.pkgs.iputils + includeos.pkgs.xorriso + ]; + + checkInputs = [ + includeos.lest + ]; + + # use `nix-build --arg doCheck true` to run tests normally + checkPhase = '' + runHook preCheck + set -e + if [ -e "${src}/${test}" ]; then + echo "Running IncludeOS test: ${src}/${test}" + python3 "${src}/${test}" + else + echo "Default test script '${test}', but no test was found 😟" + echo "For a custom path, consider specifying the path to the test script:" + echo " --argstr test 'path/to/test.py'" + exit 1 + fi + runHook postCheck + ''; + + # this is a hack + # some tests need to be run through a shell because of net_cap_raw+ep and net_cap_admin+ep + # replace nix-build with nix-shell to test without dropping capabilities + packages = [ + (includeos.pkgs.python3.withPackages (p: [ + vmrunnerPkg + ])) + ]; + shellHook = '' + set -eu + pkg="$(nix-build ./unikernel.nix --arg doCheck false --arg unikernel ${unikernel})" + + testPath="$(realpath "${unikernel}/${test}")" + cd "$pkg" + "$testPath" || exit 1 + + exit 0 + ''; + +}