From 48e601942006ab08ece9a72d1edce346e047de3e Mon Sep 17 00:00:00 2001
From: Marc Weber <marco-oweber@gmx.de>
Date: Sun, 18 Sep 2011 03:13:58 +0200
Subject: [PATCH] pxe test case

---
 tests/default.nix |    1 +
 tests/pxe.nix     |  265 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 266 insertions(+), 0 deletions(-)
 create mode 100644 tests/pxe.nix

diff --git a/tests/default.nix b/tests/default.nix
index e8c2e34..a2f4515 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -23,6 +23,7 @@ with import ../lib/testing.nix { inherit nixpkgs system; };
   proxy = makeTest (import ./proxy.nix);
   quake3 = makeTest (import ./quake3.nix);
   remote_builds = makeTest (import ./remote-builds.nix);
+  pxe = makeTest (import ./pxe.nix);
   simple = makeTest (import ./simple.nix);
   #subversion = makeTest (import ./subversion.nix);
   tomcat = makeTest (import ./tomcat.nix);
diff --git a/tests/pxe.nix b/tests/pxe.nix
new file mode 100644
index 0000000..5f429a5
--- /dev/null
+++ b/tests/pxe.nix
@@ -0,0 +1,265 @@
+# Test Nix's remote build feature.
+
+{ pkgs, ... }:
+
+with import ../lib/qemu-flags.nix;
+
+let
+
+    inherit (pkgs) lib;
+
+    protocol = "cifs"; # either samba or nfs, nfs not tested yet
+    isCifs = protocol == "cifs";
+    isNfs = protocol == "nfs";
+
+    # TODO test x86_64 and i686 !
+
+    /*
+      description:
+     This test starts a server. tftp contents (kernel, initrd) are put in /home/tftp.
+     dhcpd is configured.
+
+     The client boots a cdrom image booting from network. If you use gui of qemu by exporting display
+     be patient when logging in.
+    */
+
+
+    # TODO cleanup 
+    # mkdir -p $out/sbin
+    # cp ${pkgs.nfsUtils}/sbin/mount.nfs $out/sbin
+
+    # iso can be created by http://rom-o-matic.net/
+    # it contains a network card driver and a kernel doing a pxe boot
+    # because AFAIK qemu can't do this natively.
+    # -> image generator
+    #   Choose an output format = ISO bootable image (.iso)
+    #   Choose NIC typ          = all-drivers (you can limit this to the NIC driver of QEMU, its < 1MB anyway)
+    # -> download
+    
+    qemu_pxe_image = pkgs.fetchurl {
+      url = http://mawercer.de/~nix/gpxe-1.0.1+-gpxe.iso;
+      sha256 = "0ilsyzd34jpdpqm5bmzpa37bf0hmd3jygxn7cacnn7cyj2101xz2";
+    };
+
+    MAC = "52:54:00:12:01:" + "10"; # see qemuNICFlags
+    greeting = "Im up";
+
+    # TODO:
+    # - should cifs be used
+    # - can code from qemu-vm.nix be reused?
+    client = import ../lib/eval-config.nix {
+      # inherit nixpkgs system;
+      modules = 
+        [(  
+          { pkgs, config, ...} : {
+            # mount nfs etc (TODO)
+
+            # TODO drop either cifs or nfs? Not sure which one is easier to setup
+            # nfs does not need samba configuration
+            boot.initrd.availableKernelModules = [
+              "nfs" # nfs
+              "cifs" # samba
+              "nls_utf8"
+
+              # everything a qemu instance has loaded (tidy up!)
+              "af_packet" "nfsd" "lockd" "nfs_acl" "auth_rpcgss" "sunrpc"
+              "exportfs" "ppdev" "rtc_core"  "rtc_lib" "i2c_core" "ipv6" "jbd"
+              "mbcache" "hid" "libata" "usbcore" "scsi_mod" "nls_base"
+              "virtio_blk" "virtio_ring" "virtio_net" "virtio" "virtio_pci"
+              "unix"
+            ]; 
+
+            # .3 mean network-interfaces are up
+            networking.interfaces = [ { ipAddress = "192.168.1.3" ; name = "eth1" ; subnetMask = "255.255.255.0"; } ];
+
+            services.nfsKernel.client.enable = true;
+
+            /*
+                cp ${pkgs.dhcp}/sbin/dhclient $out/bin/
+                # no better way than this ?
+                nuke(){ sed "s|/nix/store/[a-z0-9]*-|/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-|"; }
+                dhcpNuked=$(nuke ${pkgs.dhcp})
+                mkdir -p $out/$dhcpNuked/sbin
+                cp ${pkgs.dhcp}/sbin/dhclient-script $out/$dhcpNuked/sbin/
+            */
+            boot.initrd.extraUtilsCommands =
+              ''
+                # Needing `ip' (which needs libresolv.so).
+                cp ${pkgs.iproute}/sbin/ip $out/bin
+                cp ${pkgs.glibc}/lib/libresolv.so.* $out/lib
+                cp ${pkgs.iputils}/sbin/ping $out/bin/
+                mkdir -p $out/sbin/
+              ''
+            + (lib.optionalString isNfs ''
+              cp ${pkgs.nfsUtils}/sbin/mount.nfs $out/sbin/
+            '')
+            ;
+
+            boot.initrd.postDeviceCommands =
+              ''
+                # Set up networking.  Needed for CIFS mounting.
+                # ip link set eth0 up
+                # ip addr add 192.168.1.2/24 dev eth0
+                ip link set eth1 up
+                ip addr add 192.168.1.2/24 dev eth1
+              '';
+
+            fileSystems = [
+              # let's not use disk at all, this requires enough RAM ..
+              { device = "tmpfs"; mountPoint="/"; fsType = "tmpfs"; options = "size=1024m,mode=1777"; neededForBoot = true; }
+
+            ]
+            ++ (lib.optional isNfs {
+                mountPoint = "/nix";
+                device = "192.168.1.1/nix";
+                fsType = "nfs";
+                options = "nolock"; # should be dropped when services.nfsKernel.client.enable=true is active for file locking
+                neededForBoot = true;
+              })
+            ++ (lib.optional isCifs {
+                mountPoint = "/nix";
+                device = "//192.168.1.1/nix";
+                fsType = "cifs";
+                options = "guest,sec=none,noperm,noacl";
+                neededForBoot = true;
+              })
+            ;
+
+            # send greeting to server
+            jobs = {
+              socat = {
+                startOn = "started network-interfaces";
+                script = "echo '${greeting}' | ${pkgs.socat}/bin/socat TCP-L:2345,reuseaddr -";
+              };
+            };
+          }
+         )];
+    };
+
+in
+
+{
+
+  DISPLAY="localhost:0.0";
+
+  nodes =
+    { 
+      # provides dhcpd server with boot configuration setup
+      server =
+        { config, pkgs, ... }:
+        {
+          # setup tftp contents etc
+          boot.postBootCommands = 
+          let 
+              inherit (client) config;
+              bzImage = (builtins.substring 1 9999 "${config.boot.kernelPackages.kernel}/bzImage");
+              initrd = (builtins.substring 1 99999 "${config.system.build.initialRamdisk}/initrd");
+              pxelinux_cfg = pkgs.writeText "pxelinux.cfg" '' 
+                default menu.c32
+                prompt 0
+                timeout 8
+                TOTALTIMEOUT 9000
+                LABEL nixos
+                   MENU LABEL nixos
+                   KERNEL ${bzImage}
+                   APPEND initrd=${initrd} init=${config.system.build.toplevel}/init debugtrace
+              '';
+
+          in ''
+
+            mkdir -p /home/tftp/pxelinux.cfg
+            ln -s ${pxelinux_cfg} /home/tftp/pxelinux.cfg/default
+
+            # symlink /nix/store to root so that all store contents can be
+            # accessed easily via tftp and nfs
+            ln -s /nix /home/tftp/nix
+
+            # symlink pxelinux.0 in place. Could be picked from store, but we can't put configuration files in the store easily.
+            # -> http://syslinux.zytor.com/wiki/index.php/PXELINUX -> pxelinux.configfile
+            # It can be done. but I don't know whether all pxe implementations support it
+            cp ${pkgs.syslinux}/share/syslinux/pxelinux.0 /home/tftp/
+            cp ${pkgs.syslinux}/share/syslinux/menu.c32 /home/tftp/
+          '';
+
+          environment.systemPackages = [pkgs.socat pkgs.netkittftp pkgs.nmap pkgs.nettools pkgs.tcpdump];
+
+          # offer store contents via cifs/samba and nfs
+          services.nfsKernel = {
+            client.enable = true; # TODO required?
+            server.enable = true;
+            server.exports = "/nix 192.168.1.*(ro)";
+          };
+
+          services.samba = {
+            enable = true;
+            extraConfig = ''
+              [global]
+                security = share
+              [store]
+                force user = nobody
+                path = /nix/store
+                read only = yes
+                guest ok = yes
+              [nix]
+                force user = nobody
+                path = /nix
+                read only = yes
+                guest ok = yes
+          '';
+          };
+
+          services.dhcpd = {
+            enable = true;
+            interfaces = ["eth1"];
+
+            extraConfig = let n = "192.168.1"; in ''
+
+              option domain-name "local";
+              option subnet-mask 255.255.255.0;
+              default-lease-time 600;
+              max-lease-time 7200;
+
+              subnet ${n}.0 netmask 255.255.255.0 {
+                      range ${n}.90 ${n}.100;
+                      option broadcast-address ${n}.255;
+                      option routers ${n}.1;
+              }
+
+              host pxeinstall {
+                      # the MAC of the PC which will do the network boot:
+                      hardware ethernet ${MAC};
+                      filename  "tftp://192.168.1.1/pxelinux.0";
+              }
+
+            '';
+          };
+          services.tftpd.enable = true;
+
+          virtualisation.writableStore = true;
+          virtualisation.pathsInNixDB = [ config.system.build.extraUtils ];
+        };
+
+    };
+
+  testScript = { nodes }:
+    ''
+      startAll;
+
+      $server->waitForJob("dhcpd");
+
+      # wait until tftp works, failing tftp yields empty file
+      $server->waitUntilSucceeds("echo 'get pxelinux.0' | tftp 192.168.1.1; [ \"\$(cat \"pxelinux.0\")\" != \"\" ]");
+
+      # start client by pxe network boot ..
+      # -option-rom pxe-rtl8129.bin -boot n ?
+      my $client = createMachine({ cdrom => glob("${qemu_pxe_image}"), 
+        qemuFlags => '${lib.concatStringsSep " " (qemuNICFlags 1 1 10)} '});
+      $client->start;
+
+      $server->waitUntilSucceeds("[ \"\$(socat TCP:192.168.1.3:2345 -)\" == \"${greeting}\" ]");
+
+      # uncomment and export DISPLAY to login
+      system("sleep 40m");
+    '';
+
+}
-- 
1.7.6

