summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/modules/misc/ids.nix1
-rwxr-xr-xnixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/misc/etcd.nix141
-rw-r--r--nixos/release.nix1
-rw-r--r--nixos/tests/etcd.nix108
5 files changed, 252 insertions, 0 deletions
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index b08082af352..166bb931a62 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -163,6 +163,7 @@
       systemd-resolve = 153;
       systemd-timesync = 154;
       liquidsoap = 155;
+      etcd = 156;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 19d69185392..0f09ee24027 100755
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -166,6 +166,7 @@
   ./services/misc/cgminer.nix
   ./services/misc/dictd.nix
   ./services/misc/disnix.nix
+  ./services/misc/etcd.nix
   ./services/misc/felix.nix
   ./services/misc/folding-at-home.nix
   ./services/misc/gitolite.nix
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
new file mode 100644
index 00000000000..f0693911616
--- /dev/null
+++ b/nixos/modules/services/misc/etcd.nix
@@ -0,0 +1,141 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.etcd;
+
+in {
+
+  options.services.etcd = {
+    enable = mkOption {
+      description = "Whether to enable etcd.";
+      default = false;
+      type = types.uniq types.bool;
+    };
+
+    name = mkOption {
+      description = "Etcd unique node name.";
+      default = config.networking.hostName;
+      type = types.str;
+    };
+
+    advertiseClientUrls = mkOption {
+      description = "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
+      default = cfg.listenClientUrls;
+      type = types.listOf types.str;
+    };
+
+    listenClientUrls = mkOption {
+      description = "Etcd list of URLs to listen on for client traffic.";
+      default = ["http://localhost:2379" "http://localhost:4001"];
+      type = types.listOf types.str;
+    };
+
+    listenPeerUrls = mkOption {
+      description = "Etcd list of URLs to listen on for peer traffic.";
+      default = ["http://localhost:2380" "http://localhost:7001"];
+      type = types.listOf types.str;
+    };
+
+    initialAdvertisePeerUrls = mkOption {
+      description = "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
+      default = cfg.listenPeerUrls;
+      type = types.listOf types.str;
+    };
+
+    initialCluster = mkOption {
+      description = "Etcd initial cluster configuration for bootstrapping.";
+      default = ["${cfg.name}=http://localhost:2380" "${cfg.name}=http://localhost:7001"];
+      type = types.listOf types.str;
+    };
+
+    initialClusterState = mkOption {
+      description = "Etcd initial cluster configuration for bootstrapping.";
+      default = "new";
+      type = types.enum ["new" "existing"];
+    };
+
+    initialClusterToken = mkOption {
+      description = "Etcd initial cluster token for etcd cluster during bootstrap.";
+      default = "etcd-cluster";
+      type = types.str;
+    };
+
+    discovery = mkOption {
+      description = "Etcd discovery url";
+      default = "";
+      type = types.str;
+    };
+
+    extraConf = mkOption {
+      description = ''
+        Etcd extra configuration. See
+        <link xlink:href='https://github.com/coreos/etcd/blob/master/Documentation/configuration.md#environment-variables' />
+      '';
+      type = types.attrsOf types.str;
+      default = {};
+      example = literalExample ''
+        {
+          "CORS": "*",
+          "NAME": "default-name",
+          "MAX_RESULT_BUFFER": "1024",
+          "MAX_CLUSTER_SIZE": "9",
+          "MAX_RETRY_ATTEMPTS": "3"
+        }
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/etcd";
+      description = "Etcd data directory.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.etcd = {
+      description = "Etcd Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-interfaces.target" ];
+
+      environment = {
+        ETCD_NAME = cfg.name; 
+        ETCD_DISCOVERY = cfg.discovery;
+        ETCD_DATA_DIR = cfg.dataDir;
+        ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls;
+        ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
+        ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
+        ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
+      } // (optionalAttrs (cfg.discovery == ""){
+        ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
+        ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
+        ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
+      }) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf);
+
+      serviceConfig = {
+        ExecStart = "${pkgs.etcd}/bin/etcd";
+        User = "etcd";
+        PermissionsStartOnly = true;
+      };
+      preStart = ''
+        mkdir -m 0700 -p ${cfg.dataDir}
+        if [ "$(id -u)" = 0 ]; then chown etcd ${cfg.dataDir}; fi
+      '';
+      postStart = ''
+        until ${pkgs.curl}/bin/curl -s -o /dev/null '${head cfg.listenClientUrls}/version'; do
+          sleep 1;
+        done
+      '';
+    };
+
+    environment.systemPackages = [ pkgs.etcdctl ];
+
+    users.extraUsers = singleton {
+      name = "etcd";
+      uid = config.ids.uids.etcd;
+      description = "Etcd daemon user";
+      home = cfg.dataDir;
+    };
+  };
+}
diff --git a/nixos/release.nix b/nixos/release.nix
index 83594629226..890d8d483d7 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -239,6 +239,7 @@ in rec {
   tests.chromium = callTest tests/chromium.nix {};
   tests.cjdns = callTest tests/cjdns.nix {};
   tests.containers = callTest tests/containers.nix {};
+  tests.etcd = callTest tests/etcd.nix {};
   tests.firefox = callTest tests/firefox.nix {};
   tests.firewall = callTest tests/firewall.nix {};
   tests.gnome3 = callTest tests/gnome3.nix {};
diff --git a/nixos/tests/etcd.nix b/nixos/tests/etcd.nix
new file mode 100644
index 00000000000..6c6dd84f558
--- /dev/null
+++ b/nixos/tests/etcd.nix
@@ -0,0 +1,108 @@
+# This test runs etcd as single node, multy node and using discovery
+
+import ./make-test.nix {
+  name = "etcd";
+
+  nodes = {
+    simple =
+      { config, pkgs, nodes, ... }:
+        {
+          services.etcd.enable = true;
+          services.etcd.listenClientUrls = ["http://0.0.0.0:4001"];
+          environment.systemPackages = [ pkgs.curl ];
+          networking.firewall.allowedTCPPorts = [ 4001 ];
+        };
+
+
+    node1 =
+      { config, pkgs, nodes, ... }:
+        {
+          services = {
+            etcd = {
+              enable = true;
+              listenPeerUrls = ["http://0.0.0.0:7001"];
+              initialAdvertisePeerUrls = ["http://node1:7001"];
+              initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
+            };
+          };
+
+          networking.firewall.allowedTCPPorts = [ 7001 ];
+        };
+
+    node2 =
+      { config, pkgs, ... }:
+        {
+          services = {
+            etcd = {
+              enable = true;
+              listenPeerUrls = ["http://0.0.0.0:7001"];
+              initialAdvertisePeerUrls = ["http://node2:7001"];
+              initialCluster = ["node1=http://node1:7001" "node2=http://node2:7001"];
+            };
+          };
+
+          networking.firewall.allowedTCPPorts = [ 7001 ];
+        };
+
+    discovery1 =
+      { config, pkgs, nodes, ... }:
+        {
+          services = {
+            etcd = {
+              enable = true;
+              listenPeerUrls = ["http://0.0.0.0:7001"];
+              initialAdvertisePeerUrls = ["http://discovery1:7001"];
+              discovery = "http://simple:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/";
+            };
+          };
+
+          networking.firewall.allowedTCPPorts = [ 7001 ];
+        };
+
+    discovery2 =
+      { config, pkgs, ... }:
+        {
+          services = {
+            etcd = {
+              enable = true;
+              listenPeerUrls = ["http://0.0.0.0:7001"];
+              initialAdvertisePeerUrls = ["http://discovery2:7001"];
+              discovery = "http://simple:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/";
+            };
+          };
+
+          networking.firewall.allowedTCPPorts = [ 7001 ];
+        };
+    };
+
+  testScript = ''
+    subtest "single node", sub {
+      $simple->start();
+      $simple->waitForUnit("etcd.service");
+      $simple->succeed("etcdctl set /foo/bar 'Hello world'");
+      $simple->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+    };
+
+    subtest "multy node", sub {
+      $node1->start();
+      $node2->start();
+      $node1->waitForUnit("etcd.service");
+      $node2->waitForUnit("etcd.service");
+      $node1->succeed("etcdctl set /foo/bar 'Hello world'");
+      $node2->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+      $node1->shutdown();
+      $node2->shutdown();
+    };
+
+    subtest "discovery", sub {
+      $simple->succeed("curl -X PUT http://localhost:4001/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=2");
+
+      $discovery1->start();
+      $discovery2->start();
+      $discovery1->waitForUnit("etcd.service");
+      $discovery2->waitForUnit("etcd.service");
+      $discovery1->succeed("etcdctl set /foo/bar 'Hello world'");
+      $discovery2->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+    };
+  '';
+}