1# This test runs simple etcd cluster
2
3import ./make-test.nix ({ pkgs, ... } : let
4
5 runWithOpenSSL = file: cmd: pkgs.runCommand file {
6 buildInputs = [ pkgs.openssl ];
7 } cmd;
8
9 ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
10 ca_pem = runWithOpenSSL "ca.pem" ''
11 openssl req \
12 -x509 -new -nodes -key ${ca_key} \
13 -days 10000 -out $out -subj "/CN=etcd-ca"
14 '';
15 etcd_key = runWithOpenSSL "etcd-key.pem" "openssl genrsa -out $out 2048";
16 etcd_csr = runWithOpenSSL "etcd.csr" ''
17 openssl req \
18 -new -key ${etcd_key} \
19 -out $out -subj "/CN=etcd" \
20 -config ${openssl_cnf}
21 '';
22 etcd_cert = runWithOpenSSL "etcd.pem" ''
23 openssl x509 \
24 -req -in ${etcd_csr} \
25 -CA ${ca_pem} -CAkey ${ca_key} \
26 -CAcreateserial -out $out \
27 -days 365 -extensions v3_req \
28 -extfile ${openssl_cnf}
29 '';
30
31 etcd_client_key = runWithOpenSSL "etcd-client-key.pem"
32 "openssl genrsa -out $out 2048";
33
34 etcd_client_csr = runWithOpenSSL "etcd-client-key.pem" ''
35 openssl req \
36 -new -key ${etcd_client_key} \
37 -out $out -subj "/CN=etcd-client" \
38 -config ${client_openssl_cnf}
39 '';
40
41 etcd_client_cert = runWithOpenSSL "etcd-client.crt" ''
42 openssl x509 \
43 -req -in ${etcd_client_csr} \
44 -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \
45 -out $out -days 365 -extensions v3_req \
46 -extfile ${client_openssl_cnf}
47 '';
48
49 openssl_cnf = pkgs.writeText "openssl.cnf" ''
50 ions = v3_req
51 distinguished_name = req_distinguished_name
52 [req_distinguished_name]
53 [ v3_req ]
54 basicConstraints = CA:FALSE
55 keyUsage = digitalSignature, keyEncipherment
56 extendedKeyUsage = serverAuth
57 subjectAltName = @alt_names
58 [alt_names]
59 DNS.1 = node1
60 DNS.2 = node2
61 DNS.3 = node3
62 IP.1 = 127.0.0.1
63 '';
64
65 client_openssl_cnf = pkgs.writeText "client-openssl.cnf" ''
66 ions = v3_req
67 distinguished_name = req_distinguished_name
68 [req_distinguished_name]
69 [ v3_req ]
70 basicConstraints = CA:FALSE
71 keyUsage = digitalSignature, keyEncipherment
72 extendedKeyUsage = clientAuth
73 '';
74
75 nodeConfig = {
76 services = {
77 etcd = {
78 enable = true;
79 keyFile = etcd_key;
80 certFile = etcd_cert;
81 trustedCaFile = ca_pem;
82 peerClientCertAuth = true;
83 listenClientUrls = ["https://127.0.0.1:2379"];
84 listenPeerUrls = ["https://0.0.0.0:2380"];
85 };
86 };
87
88 environment.variables = {
89 ETCDCTL_CERT_FILE = "${etcd_client_cert}";
90 ETCDCTL_KEY_FILE = "${etcd_client_key}";
91 ETCDCTL_CA_FILE = "${ca_pem}";
92 ETCDCTL_PEERS = "https://127.0.0.1:2379";
93 };
94
95 networking.firewall.allowedTCPPorts = [ 2380 ];
96 };
97in {
98 name = "etcd";
99
100 meta = with pkgs.stdenv.lib.maintainers; {
101 maintainers = [ offline ];
102 };
103
104 nodes = {
105 node1 = { config, pkgs, nodes, ... }: {
106 require = [nodeConfig];
107 services.etcd = {
108 initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
109 initialAdvertisePeerUrls = ["https://node1:2380"];
110 };
111 };
112
113 node2 = { config, pkgs, ... }: {
114 require = [nodeConfig];
115 services.etcd = {
116 initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
117 initialAdvertisePeerUrls = ["https://node2:2380"];
118 };
119 };
120
121 node3 = { config, pkgs, ... }: {
122 require = [nodeConfig];
123 services.etcd = {
124 initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380" "node3=https://node3:2380"];
125 initialAdvertisePeerUrls = ["https://node3:2380"];
126 initialClusterState = "existing";
127 };
128 };
129 };
130
131 testScript = ''
132 subtest "should start etcd cluster", sub {
133 $node1->start();
134 $node2->start();
135 $node1->waitForUnit("etcd.service");
136 $node2->waitForUnit("etcd.service");
137 $node2->waitUntilSucceeds("etcdctl cluster-health");
138 $node1->succeed("etcdctl set /foo/bar 'Hello world'");
139 $node2->succeed("etcdctl get /foo/bar | grep 'Hello world'");
140 };
141
142 subtest "should add another member", sub {
143 $node1->succeed("etcdctl member add node3 https://node3:2380");
144 $node3->start();
145 $node3->waitForUnit("etcd.service");
146 $node3->waitUntilSucceeds("etcdctl member list | grep 'node3'");
147 $node3->succeed("etcdctl cluster-health");
148 };
149
150 subtest "should survive member crash", sub {
151 $node3->crash;
152 $node1->succeed("etcdctl cluster-health");
153 $node1->succeed("etcdctl set /foo/bar 'Hello degraded world'");
154 $node1->succeed("etcdctl get /foo/bar | grep 'Hello degraded world'");
155 };
156 '';
157})