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