From 520a2f809e5971aaa80d5089e636d654f88581f1 Mon Sep 17 00:00:00 2001 From: Tomas Antonio Lopez Date: Wed, 28 Dec 2022 00:13:06 +0100 Subject: [PATCH 1/2] Add support for native NixOS containers Module + tests --- build.nix | 3 + configure.ac | 9 ++ dysnomia-module.nix | 2 + dysnomia-modules/Makefile.am | 5 + dysnomia-modules/nixos-container.in | 106 +++++++++++++++++ release.nix | 4 + .../nixos-container/configuration.nix | 32 ++++++ .../nixos-container/test-container-settings | 1 + tests/nixos-container.nix | 108 ++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 dysnomia-modules/nixos-container.in create mode 100644 tests/deployment/nixos-container/configuration.nix create mode 100644 tests/deployment/nixos-container/test-container-settings create mode 100644 tests/nixos-container.nix diff --git a/build.nix b/build.nix index 94ad97a..39e8703 100644 --- a/build.nix +++ b/build.nix @@ -6,6 +6,7 @@ , enableTomcatWebApplication ? false , enableMongoDatabase ? false , enableNginxWebApplication ? false +, enableNixosContainer ? false , enableSubversionRepository ? false , enableInfluxDatabase ? false , enableSupervisordProgram ? false @@ -33,6 +34,7 @@ pkgs.releaseTools.nixBuild { (if enablePostgreSQLDatabase then "--with-postgresql" else "--without-postgresql") (if enableMongoDatabase then "--with-mongodb" else "--without-mongodb") (if enableNginxWebApplication then "--with-nginx" else "--without-nginx") + (if enableNixosContainer then "--with-nixos-container" else "--without-nixos-container") (if enableTomcatWebApplication then "--with-tomcat=${catalinaBaseDir}" else "--without-tomcat") (if enableSubversionRepository then "--with-subversion" else "--without-subversion") (if enableInfluxDatabase then "--with-influxdb" else "--without-influxdb") @@ -52,6 +54,7 @@ pkgs.releaseTools.nixBuild { ++ pkgs.lib.optional enableMongoDatabase pkgs.mongodb ++ pkgs.lib.optional enableMongoDatabase pkgs.mongodb-tools ++ pkgs.lib.optional enableNginxWebApplication pkgs.nginx + ++ pkgs.lib.optional enableNixosContainer pkgs.nixos-container ++ pkgs.lib.optional enableSubversionRepository pkgs.subversion ++ pkgs.lib.optional enableInfluxDatabase pkgs.influxdb ++ pkgs.lib.optional enableSystemdUnit pkgs.systemd diff --git a/configure.ac b/configure.ac index 802796d..38981b7 100644 --- a/configure.ac +++ b/configure.ac @@ -17,6 +17,7 @@ AC_ARG_WITH([mysql], AS_HELP_STRING([--without-mysql], [Ignore presence of MySQL AC_ARG_WITH([postgresql], AS_HELP_STRING([--without-postgresql], [Ignore presence of PostgreSQL and disable it])) AC_ARG_WITH([mongodb], AS_HELP_STRING([--without-mongodb], [Ignore presence of MongoDB and disable it])) AC_ARG_WITH([nginx], AS_HELP_STRING([--without-nginx], [Ignore presence of Nginx and disable it])) +AC_ARG_WITH([nixos-container], AS_HELP_STRING([--without-nixos-container], [Ignore presence of support for NixOS containers])) AC_ARG_WITH([influxdb], AS_HELP_STRING([--without-influxdb], [Ignore presence of InfluxDB and disable it])) AC_ARG_WITH([tomcat], AS_HELP_STRING([--with-tomcat], [Enable Apache Tomcat module and specifies location of the Apache Tomcat base directory])) AC_ARG_WITH([subversion], AS_HELP_STRING([--with-subversion], [Enable subversion repository module])) @@ -94,6 +95,9 @@ AS_IF([test "x$with_nginx" != "x" && test "x$with_nginx" != "xno"], [have_nginx=yes], [have_nginx=no]) +AS_IF([test "x$with_nixos_container" != "xno"], + [AC_PATH_PROG(nixos_container_bin, nixos-container)]) + AS_IF([test "x$with_postgresql" != "xno"], [AC_PATH_PROG(createdb, createdb)]) @@ -194,6 +198,7 @@ AM_CONDITIONAL(have_mysql, [test x$mysql != "x"]) AM_CONDITIONAL(have_postgresql, [test x$psql != "x"]) AM_CONDITIONAL(have_mongodb, [test x$mongo != "x"]) AM_CONDITIONAL(have_nginx, [test x$have_nginx != "xno"]) +AM_CONDITIONAL(have_nixos_container, [test x$nixos_container_bin != "x"]) AM_CONDITIONAL(have_influxdb, [test x$influx != "x"]) AM_CONDITIONAL(have_tomcat, [test x$have_tomcat != "xno"]) AM_CONDITIONAL(have_subversion, [test x$svnadmin != "x"]) @@ -241,6 +246,9 @@ AS_IF([test "x$mongo" != "x"], AS_IF([test "x$have_nginx" != "xno"], [nginx_webapplication=dysnomia-modules/nginx-webapplication]) +AS_IF([test "x$nixos_container_bin" != "x"], + [nixos_container=dysnomia-modules/nixos-container]) + AS_IF([test "x$influx" != "x"], [influx_database=dysnomia-modules/influx-database]) @@ -321,6 +329,7 @@ $mysql_database $postgresql_database $mongo_database $nginx_webapplication +$nixos_container $influx_database $tomcat_webapplication $subversion_repository diff --git a/dysnomia-module.nix b/dysnomia-module.nix index c06926e..3e29328 100644 --- a/dysnomia-module.nix +++ b/dysnomia-module.nix @@ -72,6 +72,7 @@ let enableDockerContainer = config.virtualisation.docker.enable; enableEjabberdDump = config.services.ejabberd.enable; enableMySQLDatabase = config.services.mysql.enable; + enableNixosContainer = config.boot.enableContainers; enablePostgreSQLDatabase = config.services.postgresql.enable; enableTomcatWebApplication = config.services.tomcat.enable; enableMongoDatabase = config.services.mongodb.enable; @@ -183,6 +184,7 @@ in ++ optional (dysnomiaFlags.enableEjabberdDump) "ejabberd-dump" ++ optional (dysnomiaFlags.enableInfluxDatabase) "influx-database" ++ optional (dysnomiaFlags.enableMySQLDatabase) "mysql-database" + ++ optional (dysnomiaFlags.enableNixosContainer) "nixos-container" ++ optional (dysnomiaFlags.enablePostgreSQLDatabase) "postgresql-database" ++ optional (dysnomiaFlags.enableTomcatWebApplication) "tomcat-webapplication" ++ optional (dysnomiaFlags.enableMongoDatabase) "mongo-database" diff --git a/dysnomia-modules/Makefile.am b/dysnomia-modules/Makefile.am index 18b0623..799e0c9 100644 --- a/dysnomia-modules/Makefile.am +++ b/dysnomia-modules/Makefile.am @@ -48,6 +48,10 @@ if have_nginx libexec_SCRIPTS += nginx-webapplication endif +if have_nixos_container + libexec_SCRIPTS += nixos-container +endif + if have_postgresql libexec_SCRIPTS += postgresql-database endif @@ -116,6 +120,7 @@ EXTRA_DIST = apache-webapplication.in \ mysql-database.in \ nginx-webapplication.in \ nixos-configuration.in \ + nixos-container.in \ postgresql-database.in \ process.in \ s6-rc-service.in \ diff --git a/dysnomia-modules/nixos-container.in b/dysnomia-modules/nixos-container.in new file mode 100644 index 0000000..5f07483 --- /dev/null +++ b/dysnomia-modules/nixos-container.in @@ -0,0 +1,106 @@ +#!/bin/bash +set -e +shopt -s nullglob + +# Activates or deactivates a NixOS container +# +# Files: +# *-container-settings: Containing global container configuration settings +# *-container-createparams: Linefeed delimited configuration file containing additional command-line arguments passed to: nixos-container create +# +# A settings file is key=value pair based, and supports the following properties: +# containerName: Name of the container (defaults to: $componentName) +# statelessDeployment: If enabled, the container will be removed if it is no +# longer used by any other containers (defaults to: 1) + +# Autoconf settings +export prefix=@prefix@ + +# Import utility functions +source @datadir@/@PACKAGE@/util + +# Sets a number of common utility environment variables +composeUtilityVariables $0 $2 $3 + +source $2/*-container-settings + +# Provide default settings +containerName=${containerName:-"$componentName"} +statelessDeployment=${statelessDeployment:-1} + +destroyContainer() +{ + if [ "$(@nixos_container_bin@ status "$containerName")" = "down" ] + then + @nixos_container_bin@ destroy $containerName + fi +} + +case "$1" in + activate) + # Create the container if it does not exist yet + if [ "$(@nixos_container_bin@ status "$containerName")" = "gone" ] + then + ( + echo "$containerName" + cat $2/*-container-createparams + echo "" + + # If a flake ref has been specified, use that + if [ -n "$flakeRef" ] + then + echo "--flake" + echo "$flakeRef" + else + echo "--config-file" + echo "$2/configuration.nix" + fi + ) | @xargs@ -d '\n' @nixos_container_bin@ create + fi + + @nixos_container_bin@ start $containerName + + if [ "$statelessDeployment" != "1" ] + then + markComponentAsActive + fi + ;; + deactivate) + # Stop the container if it is still running + if [ "$(@nixos_container_bin@ status "$containerName")" = "up" ] + then + @nixos_container_bin@ stop $containerName + fi + + if [ "$statelessDeployment" = "1" ] + then + destroyContainer + else + markComponentAsGarbage + fi + ;; + collect-garbage) + if componentMarkedAsGarbage + then + destroyContainer + unmarkComponentAsGarbage + fi + ;; + shell) + cat >&2 < # Run inside the container +EOF + ;; +esac diff --git a/release.nix b/release.nix index fb4cddb..3da1c37 100644 --- a/release.nix +++ b/release.nix @@ -65,6 +65,10 @@ let enableState = true; }; + nixos-container = import ./tests/nixos-container.nix { + inherit nixpkgs tarball buildFun; + }; + influx-database = import ./tests/influx-database.nix { inherit nixpkgs tarball buildFun; }; diff --git a/tests/deployment/nixos-container/configuration.nix b/tests/deployment/nixos-container/configuration.nix new file mode 100644 index 0000000..797ec19 --- /dev/null +++ b/tests/deployment/nixos-container/configuration.nix @@ -0,0 +1,32 @@ +{config, lib, pkgs, ...}: + +with lib; +{ + # Minimal container config, as written in the nixos-container script + boot.isContainer = true; + networking.hostName = mkDefault "test"; + networking.useDHCP = false; + + # Run a simple TCP echo server + services.xinetd = { + enable = true; + # This ugly hack is necessary because the NixOS service does not support + # INTERNAL services + extraDefaults = '' + } + + service echo + { + protocol = tcp + type = INTERNAL + socket_type = stream + wait = no + ''; + }; + + # Open the necessary port + networking.firewall.allowedTCPPorts = [ 7 ]; + + # Silence the warning + system.stateVersion = "22.11"; +} diff --git a/tests/deployment/nixos-container/test-container-settings b/tests/deployment/nixos-container/test-container-settings new file mode 100644 index 0000000..4d2c3c3 --- /dev/null +++ b/tests/deployment/nixos-container/test-container-settings @@ -0,0 +1 @@ +containerName=test \ No newline at end of file diff --git a/tests/nixos-container.nix b/tests/nixos-container.nix new file mode 100644 index 0000000..5ac3e15 --- /dev/null +++ b/tests/nixos-container.nix @@ -0,0 +1,108 @@ +{ nixpkgs, tarball, buildFun }: + +let + dysnomia = buildFun { + pkgs = import nixpkgs {}; + inherit tarball; + enableNixosContainer = true; + }; + + pkgs = import nixpkgs {}; +in +with import "${nixpkgs}/nixos/lib/testing-python.nix" { system = builtins.currentSystem; }; +with pkgs; + +let + # Pre-evaluate the container derivation, otherwise the VM needs to do it and + # there is no Internet connection in there + sysEval = import "${nixpkgs}/nixos/lib/eval-config.nix"; + container = (sysEval { + system = builtins.currentSystem; + modules = [ + ./deployment/nixos-container/configuration.nix + ]; + }).config.system.build.toplevel; + + component = symlinkJoin { + name = "nixos-container-component"; + paths = [ + # This defines a simple container implementing the TCP echo protocol + ./deployment/nixos-container + # Parameters passed to nixos-container need to be created dynamically + # because we have to provide a path to the pre-built derivation of the + # container + (runCommandLocal "test-container-createparams-cmd" {} '' + mkdir $out + + cat >> $out/test-container-createparams << EOF + --host-address + 10.235.0.1 + --local-address + 10.235.0.2 + --system-path + ${container} + EOF + '').out + ]; + }; + + addr = "10.235.0.2"; +in +makeTest { + name = "dysnomia-nixos-container-test"; + nodes = { + machine = {config, pkgs, ...}: + + { + virtualisation.memorySize = 512; + virtualisation.diskSize = 1024; + boot.enableContainers = true; + + environment.systemPackages = [ dysnomia ]; + }; + }; + + testScript = + '' + # This pipeline will write "test" to stdout upon success + testcmd = "echo \"test\" | nc -N ${addr} 7" + + def check_container_activated(): + machine.succeed("sleep 5") + # Compare output to the expected string + machine.succeed("test " + testcmd + " = \"test\"") + + + def check_container_deactivated(): + machine.succeed("sleep 5") + # Netcat fails with code 1 if the machine is inactive + machine.fail(testcmd) + + + # Activate the test container and verify that the TCP echo server is working + machine.succeed( + "dysnomia --type nixos-container --operation activate --component ${component} --environment" + ) + check_container_activated() + + # Activate again. This operation should succeed as it is idempotent. + machine.succeed( + "dysnomia --type nixos-container --operation activate --component ${component} --environment" + ) + check_container_activated() + + # Deactivate the process. Check if the container is not running anymore, and verify that it has been removed. + machine.succeed( + "dysnomia --type nixos-container --operation deactivate --component ${component} --environment" + ) + check_container_deactivated() + + machine.succeed("test $(nixos-container list | wc -l) = 0") + + # Deactivate again. This operation should succeed as it is idempotent. + machine.succeed( + "dysnomia --type nixos-container --operation deactivate --component ${component} --environment" + ) + check_container_deactivated() + ''; +} From a7915fe1b1305116d965a276c92e9ae5bc4e5ed7 Mon Sep 17 00:00:00 2001 From: Tomas Antonio Lopez Date: Thu, 29 Dec 2022 01:16:21 +0100 Subject: [PATCH 2/2] Suppress nixos-container error upon creation This comes from the first time we perform the test at line 42 of nixos-container.in: since the container is not there yet, nixos-container barks an error, while sending "gone" to stdout (which is what we are interested in). --- dysnomia-modules/nixos-container.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dysnomia-modules/nixos-container.in b/dysnomia-modules/nixos-container.in index 5f07483..e00039a 100644 --- a/dysnomia-modules/nixos-container.in +++ b/dysnomia-modules/nixos-container.in @@ -39,7 +39,7 @@ destroyContainer() case "$1" in activate) # Create the container if it does not exist yet - if [ "$(@nixos_container_bin@ status "$containerName")" = "gone" ] + if [ "$(@nixos_container_bin@ status "$containerName" 2> /dev/null)" = "gone" ] then ( echo "$containerName"