| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  | # This test runs simple etcd cluster | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-24 01:38:51 +01:00
										 |  |  | import ./make-test-python.nix ({ pkgs, ... } : let | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-26 22:23:27 -04:00
										 |  |  |   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" ''
 | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |     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 | 
					
						
							| 
									
										
										
										
											2016-08-26 22:23:27 -04:00
										 |  |  |   '';
 | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-26 22:23:27 -04:00
										 |  |  |   client_openssl_cnf = pkgs.writeText "client-openssl.cnf" ''
 | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |     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; | 
					
						
							| 
									
										
										
										
											2016-08-26 22:23:27 -04:00
										 |  |  |         keyFile = etcd_key; | 
					
						
							|  |  |  |         certFile = etcd_cert; | 
					
						
							|  |  |  |         trustedCaFile = ca_pem; | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |         peerClientCertAuth = true; | 
					
						
							|  |  |  |         listenClientUrls = ["https://127.0.0.1:2379"]; | 
					
						
							|  |  |  |         listenPeerUrls = ["https://0.0.0.0:2380"]; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     environment.variables = { | 
					
						
							| 
									
										
										
										
											2016-08-26 22:23:27 -04:00
										 |  |  |       ETCDCTL_CERT_FILE = "${etcd_client_cert}"; | 
					
						
							|  |  |  |       ETCDCTL_KEY_FILE = "${etcd_client_key}"; | 
					
						
							|  |  |  |       ETCDCTL_CA_FILE = "${ca_pem}"; | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |       ETCDCTL_PEERS = "https://127.0.0.1:2379"; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     networking.firewall.allowedTCPPorts = [ 2380 ]; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | in { | 
					
						
							|  |  |  |   name = "etcd"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 20:08:30 +01:00
										 |  |  |   meta = with pkgs.lib.maintainers; { | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |     maintainers = [ offline ]; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   nodes = { | 
					
						
							| 
									
										
										
										
											2018-07-20 20:56:59 +00:00
										 |  |  |     node1 = { ... }: { | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |       require = [nodeConfig]; | 
					
						
							|  |  |  |       services.etcd = { | 
					
						
							|  |  |  |         initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"]; | 
					
						
							|  |  |  |         initialAdvertisePeerUrls = ["https://node1:2380"]; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-20 20:56:59 +00:00
										 |  |  |     node2 = { ... }: { | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |       require = [nodeConfig]; | 
					
						
							|  |  |  |       services.etcd = { | 
					
						
							|  |  |  |         initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"]; | 
					
						
							|  |  |  |         initialAdvertisePeerUrls = ["https://node2:2380"]; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-20 20:56:59 +00:00
										 |  |  |     node3 = { ... }: { | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |       require = [nodeConfig]; | 
					
						
							|  |  |  |       services.etcd = { | 
					
						
							|  |  |  |         initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380" "node3=https://node3:2380"]; | 
					
						
							|  |  |  |         initialAdvertisePeerUrls = ["https://node3:2380"]; | 
					
						
							|  |  |  |         initialClusterState = "existing"; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   testScript = ''
 | 
					
						
							| 
									
										
										
										
											2019-11-24 01:38:51 +01:00
										 |  |  |     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'") | 
					
						
							| 
									
										
										
										
											2016-08-25 14:41:47 +02:00
										 |  |  |   '';
 | 
					
						
							|  |  |  | }) |