diff --git a/flake.nix b/flake.nix index 37cfbc2..15b1a35 100644 --- a/flake.nix +++ b/flake.nix @@ -139,15 +139,12 @@ checks = # Checks for continous testing + let tests = import ./tests; + in with (forAllCrossSystems ({ system, localSystem, crossSystem }: - import ./tests { - inherit self; - apps = self.apps.${system}; - localPackages = nixpkgsFor.${localSystem}; - genodepkgs = self.packages.${system}; - lib = self.lib.${system}; - nixpkgs = nixpkgsFor.${system}; - legacyPackages = self.legacyPackages.${system}; + tests { + flake = self; + inherit system localSystem crossSystem; } // { ports = nixpkgsFor.${localSystem}.symlinkJoin { name = "ports"; diff --git a/nixos-modules/genode-init.nix b/nixos-modules/genode-init.nix new file mode 100644 index 0000000..971c6c8 --- /dev/null +++ b/nixos-modules/genode-init.nix @@ -0,0 +1,93 @@ +# genodeInit.children is an attrset of nixos configurations, like containers + +{ config, pkgs, lib, ... }: + +with lib; + +{ + imports = [ ]; + + options.genode.init = { + + config = mkOption { + description = "Dhall configuration of this init instance"; + type = types.either types.str types.path; + }; + + inputs = mkOption { + description = "List of packages to build a ROM store with."; + type = types.listOf types.package; + }; + + subinits = mkOption { + type = types.attrsOf (types.submodule ({ config, options, name, ... }: { + options = { + + config = mkOption { + description = '' + A specification of the desired configuration of this sub-init, as a NixOS module. + ''; + type = + let confPkgs = if config.pkgs == null then pkgs else config.pkgs; + in lib.mkOptionType { + name = "Toplevel NixOS config"; + merge = loc: defs: + (import (confPkgs.path + "/nixos/lib/eval-config.nix") { + inherit system; + pkgs = confPkgs; + baseModules = + import (confPkgs.path + "/nixos/modules/module-list.nix"); + inherit (confPkgs) lib; + modules = let + extraConfig = { + _file = "module at ${__curPos.file}:${ + toString __curPos.line + }"; + config = { }; + }; + in [ extraConfig ] ++ (map (x: x.value) defs); + prefix = [ "containers" name ]; + }).config; + }; + }; + + pkgs = mkOption { + type = types.nullOr types.attrs; + default = null; + example = literalExample "pkgs"; + description = '' + Customise which nixpkgs to use for this container. + ''; + }; + + }; + + config = mkMerge [ + (mkIf options.config.isDefined { + path = config.config.system.build.toplevel; + }) + ]; + })); + + default = { }; + }; + + }; + + config = { + + system.build.initXml = pkgs.buildPackages.runCommand "init.xml" { + nativeBuildInputs = with pkgs.buildPackages; [ dhall xorg.lndir ]; + DHALL_GENODE = "${pkgs.genodePackages.dhallGenode}/binary.dhall"; + INIT_CONFIG = config.genode.init.config; + } '' + export XDG_CACHE_HOME=$NIX_BUILD_TOP + lndir -silent \ + ${pkgs.genodePackages.dhallGenode}/.cache \ + $XDG_CACHE_HOME + dhall text <<< "(env:DHALL_GENODE).Init.render (env:INIT_CONFIG)" > $out + ''; + + }; + +} diff --git a/tests/default.nix b/tests/default.nix index b4d99d2..053a3fe 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,21 +1,31 @@ -{ self, apps, localPackages, genodepkgs, lib, nixpkgs, legacyPackages }: +{ flake, system, localSystem, crossSystem }: + +let + apps = flake.apps.${system}; + localPackages = flake.legacyPackages.${localSystem}; + genodepkgs = flake.packages.${system}; + lib = flake.lib.${system}; + nixpkgs = flake.legacyPackages.${system}; + legacyPackages = flake.legacyPackages.${system}; + +in with import ./lib/build-vms.nix { + inherit flake system localSystem crossSystem; + pkgs = flake.legacyPackages.${system}; + localPackages = flake.inputs.nixpkgs.legacyPackages.${localSystem}; + modulesPath = "${flake.inputs.nixpkgs}/nixos/modules"; +}; +with flake.legacyPackages.${system}; let - callTest = path: - import path { - pkgs = testPkgs; - inherit nixpkgs localPackages legacyPackages; - }; - - testFiles = map callTest [ + testSpecs = map (p: import p) [ ./log.nix - ./posix.nix - ./tox-bootstrapd.nix - ./vmm_arm.nix - ./vmm_x86.nix - ./x86.nix - ] ++ (callTest ./solo5); + # ./posix.nix + # ./tox-bootstrapd.nix + # ./vmm_arm.nix + # ./vmm_x86.nix + # ./x86.nix + ]; # TODO ++ (callTest ./solo5); testPkgs = genodepkgs; @@ -27,6 +37,7 @@ let x86_64-genode = "${qemuPkg}/bin/qemu-system-x86_64"; }.${genodepkgs.stdenv.hostPlatform.system}; + # TODO: move the cores into nixos modules cores = [ { prefix = "hw-pc-"; @@ -141,6 +152,7 @@ let let testDriverName = "genode-test-driver-${name}"; + # TODO: move buildVM into a nixos module buildVM = vmName: { config, inputs, env ? { }, extraPaths ? [ ] }: let @@ -178,7 +190,6 @@ let ./test-wrapper.dhall } (${config}) $(stat --format '%s' ${storeTarball}/store.tar) ${storeManifest} ${manifest}"; env' = { - DHALL_GENODE = "${testPkgs.dhallGenode}/source.dhall"; DHALL_GENODE_TEST = "${./test.dhall}"; } // env; @@ -197,7 +208,10 @@ let ''${apps.render-init.program} <<< "(${config'}).config" > $out''; }; - nodes = lib.mapAttrs buildVM + # nodes = lib.mapAttrs buildVM + # (t.nodes or (if t ? machine then { machine = t.machine; } else { })); + + nodes = buildVirtualNetwork (t.nodes or (if t ? machine then { machine = t.machine; } else { })); testScript' = @@ -207,7 +221,9 @@ let else testScript; - vms = map (node: node.script) (lib.attrValues nodes); + vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes); + + vms = map (m: m.config.system.build.vm) (lib.attrValues nodes); # Generate onvenience wrappers for running the test driver # interactively with the specified network, and for starting the @@ -285,7 +301,7 @@ let } else null; - in lib.lists.crossLists f [ cores' testFiles ]; + in lib.lists.crossLists f [ cores' testSpecs ]; in builtins.listToAttrs (builtins.filter (_: _ != null) testList) diff --git a/tests/lib/build-vms.nix b/tests/lib/build-vms.nix new file mode 100644 index 0000000..ca6e4e3 --- /dev/null +++ b/tests/lib/build-vms.nix @@ -0,0 +1,109 @@ +{ flake, system, localSystem, crossSystem +# Nixpkgs, for qemu, lib and more +, localPackages, pkgs, modulesPath }: + +with pkgs.lib; +with import ./qemu-flags.nix { inherit pkgs; }; + +rec { + + inherit pkgs; + + qemu = pkgs.qemu_test; + + # Build a virtual network from an attribute set `{ machine1 = + # config1; ... machineN = configN; }', where `machineX' is the + # hostname and `configX' is a NixOS system configuration. Each + # machine is given an arbitrary IP address in the virtual network. + buildVirtualNetwork = nodes: + let nodesOut = mapAttrs (n: buildVM nodesOut) (assignIPAddresses nodes); + in nodesOut; + + buildVM = nodes: configurations: + + import "${modulesPath}/../lib/eval-config.nix" { + inherit system; + modules = configurations; + baseModules = (import "${modulesPath}/module-list.nix") ++ [ + "${modulesPath}/virtualisation/qemu-vm.nix" + "${modulesPath}/testing/test-instrumentation.nix" # !!! should only get added for automated test runs + { + key = "no-manual"; + documentation.nixos.enable = false; + } + { + key = "qemu"; + system.build.qemu = qemu; + } + { + key = "nodes"; + _module.args.nodes = nodes; + } + { + nixpkgs = { + inherit system crossSystem localSystem; + pkgs = flake.legacyPackages.${system}; + }; + } + ]; + }; + + # Given an attribute set { machine1 = config1; ... machineN = + # configN; }, sequentially assign IP addresses in the 192.168.1.0/24 + # range to each machine, and set the hostname to the attribute name. + assignIPAddresses = nodes: + + let + + machines = attrNames nodes; + + machinesNumbered = zipLists machines (range 1 254); + + nodes_ = forEach machinesNumbered (m: + nameValuePair m.fst [ + ({ config, nodes, ... }: + let + interfacesNumbered = + zipLists config.virtualisation.vlans (range 1 255); + interfaces = forEach interfacesNumbered ({ fst, snd }: + nameValuePair "eth${toString snd}" { + ipv4.addresses = [{ + address = "192.168.${toString fst}.${toString m.snd}"; + prefixLength = 24; + }]; + }); + in { + key = "ip-address"; + config = { + networking.hostName = mkDefault m.fst; + + networking.interfaces = listToAttrs interfaces; + + networking.primaryIPAddress = optionalString (interfaces != [ ]) + (head (head interfaces).value.ipv4.addresses).address; + + # Put the IP addresses of all VMs in this machine's + # /etc/hosts file. If a machine has multiple + # interfaces, use the IP address corresponding to + # the first interface (i.e. the first network in its + # virtualisation.vlans option). + networking.extraHosts = flip concatMapStrings machines (m': + let config = (getAttr m' nodes).config; + in optionalString (config.networking.primaryIPAddress != "") + ("${config.networking.primaryIPAddress} " + + optionalString (config.networking.domain != null) + "${config.networking.hostName}.${config.networking.domain} " + + '' + ${config.networking.hostName} + '')); + + virtualisation.qemu.options = forEach interfacesNumbered + ({ fst, snd }: qemuNICFlags snd fst m.snd); + }; + }) + (getAttr m.fst nodes) + ]); + + in listToAttrs nodes_; + +} diff --git a/tests/lib/qemu-flags.nix b/tests/lib/qemu-flags.nix new file mode 100644 index 0000000..8c47c3a --- /dev/null +++ b/tests/lib/qemu-flags.nix @@ -0,0 +1,32 @@ +# QEMU flags shared between various Nix expressions. +{ pkgs }: + +let + zeroPad = n: + pkgs.lib.optionalString (n < 16) "0" + + (if n > 255 + then throw "Can't have more than 255 nets or nodes!" + else pkgs.lib.toHexString n); +in + +rec { + qemuNicMac = net: machine: "52:54:00:12:${zeroPad net}:${zeroPad machine}"; + + qemuNICFlags = nic: net: machine: + [ "-device virtio-net-pci,netdev=vlan${toString nic},mac=${qemuNicMac net machine}" + "-netdev vde,id=vlan${toString nic},sock=$QEMU_VDE_SOCKET_${toString net}" + ]; + + qemuSerialDevice = if pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64 then "ttyS0" + else if pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64 then "ttyAMA0" + else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'"; + + qemuBinary = qemuPkg: { + x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu max"; + armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -enable-kvm -machine virt -cpu host"; + aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -enable-kvm -machine virt,gic-version=host -cpu host"; + x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu max"; + aarch64-genode = "${qemuPkg}/bin/qemu-system-aarch64 -M virt,virtualization=true,gic_version=3 -cpu cortex-a53"; + x86_64-genode = "${qemuPkg}/bin/qemu-system-x86_64 -machine q35"; + }.${pkgs.stdenv.hostPlatform.system} or "${qemuPkg}/bin/qemu-kvm"; +} diff --git a/tests/log.dhall b/tests/log.dhall index f5bc194..9582863 100644 --- a/tests/log.dhall +++ b/tests/log.dhall @@ -1,10 +1,10 @@ -let Test = ./test.dhall ? env:DHALL_GENODE_TEST - -let Genode = Test.Genode +let Genode = + env:DHALL_GENODE + ? https://git.sr.ht/~ehmry/dhall-genode/blob/master/package.dhall let Child = Genode.Init.Child -in Test::{ +in Genode.Init::{ , children = toMap { test-log = Child.flat diff --git a/tests/log.nix b/tests/log.nix index bd6f935..05a7395 100644 --- a/tests/log.nix +++ b/tests/log.nix @@ -1,11 +1,13 @@ -{ pkgs, ... }: -with pkgs; - { name = "log"; - machine = { - config = ./log.dhall; - inputs = [ (pkgs.genodeSources.depot "test-log") ]; + machine = { config, pkgs, ... }: { + imports = [ + ../nixos-modules/genode-init.nix + ]; + genode.init = { + config = ./log.dhall; + inputs = [ (pkgs.genodeSources.depot "test-log") ]; + }; }; testScript = '' start_all()