Skip to content

Commit 9d3a7bd

Browse files
committed
TUN-4125: Change component tests to run in CI with its own dedicated resources
1 parent 1cf6ae3 commit 9d3a7bd

File tree

5 files changed

+170
-40
lines changed

5 files changed

+170
-40
lines changed

cfsetup.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,11 @@ stretch: &stretch
220220
pre-cache:
221221
- sudo pip3 install --upgrade -r component-tests/requirements.txt
222222
post-cache:
223-
# Constructs config file from env vars
224-
- python3 component-tests/config.py
223+
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
224+
- python3 component-tests/setup.py --type create
225225
- pytest component-tests
226+
# The Named Tunnel is deleted and its route unprovisioned here.
227+
- python3 component-tests/setup.py --type cleanup
226228
update-homebrew:
227229
builddeps:
228230
- openssh-client

component-tests/config.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
#!/usr/bin/env python
2-
import base64
32
import copy
4-
import os
5-
import yaml
63

74
from dataclasses import dataclass, InitVar
85

96
from constants import METRICS_PORT
107

11-
from util import LOGGER
12-
138
# frozen=True raises exception when assigning to fields. This emulates immutability
149

1510

@@ -98,35 +93,3 @@ def __post_init__(self, additional_config):
9893

9994
def get_url(self):
10095
return "https://" + self.hostname
101-
102-
103-
def build_config_from_env():
104-
config_path = get_env("COMPONENT_TESTS_CONFIG")
105-
config_content = base64.b64decode(
106-
get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
107-
config_yaml = yaml.safe_load(config_content)
108-
109-
credentials_file = get_env("COMPONENT_TESTS_CREDENTIALS_FILE")
110-
write_file(credentials_file, config_yaml["credentials_file"])
111-
112-
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
113-
write_file(origincert, config_yaml["origincert"])
114-
115-
write_file(config_content, config_path)
116-
117-
118-
def write_file(content, path):
119-
with open(path, 'w') as outfile:
120-
outfile.write(content)
121-
outfile.close
122-
123-
124-
def get_env(env_name):
125-
val = os.getenv(env_name)
126-
if val is None:
127-
raise Exception(f"{env_name} is not set")
128-
return val
129-
130-
131-
if __name__ == '__main__':
132-
build_config_from_env()

component-tests/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
cloudflare==2.8.15
12
flaky==3.7.0
23
pytest==6.2.2
34
pyyaml==5.4.1

component-tests/setup.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env python
2+
import argparse
3+
import base64
4+
import json
5+
import os
6+
import subprocess
7+
import uuid
8+
9+
import CloudFlare
10+
import yaml
11+
from retrying import retry
12+
13+
from constants import MAX_RETRIES, BACKOFF_SECS
14+
from util import LOGGER
15+
16+
17+
def get_config_from_env():
18+
config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
19+
return yaml.safe_load(config_content)
20+
21+
22+
def get_config_from_file():
23+
config_path = get_env("COMPONENT_TESTS_CONFIG")
24+
with open(config_path, 'r') as infile:
25+
return yaml.safe_load(infile)
26+
27+
28+
def persist_config(config):
29+
config_path = get_env("COMPONENT_TESTS_CONFIG")
30+
with open(config_path, 'w') as outfile:
31+
yaml.safe_dump(config, outfile)
32+
33+
34+
def persist_origin_cert(config):
35+
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
36+
path = config["origincert"]
37+
with open(path, 'w') as outfile:
38+
outfile.write(origincert)
39+
return path
40+
41+
42+
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
43+
def create_tunnel(config, origincert_path, random_uuid):
44+
# Delete any previous existing credentials file. If the agent keeps files around (that's the case in Windows) then
45+
# cloudflared tunnel create will refuse to create the tunnel because it does not want to overwrite credentials
46+
# files.
47+
credentials_path = config["credentials_file"]
48+
try:
49+
os.remove(credentials_path)
50+
except OSError:
51+
pass
52+
53+
tunnel_name = "cfd_component_test-" + random_uuid
54+
create_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "create",
55+
"--credentials-file", credentials_path, tunnel_name]
56+
LOGGER.info(f"Creating tunnel with {create_cmd}")
57+
subprocess.run(create_cmd, check=True)
58+
59+
list_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "list", "--name",
60+
tunnel_name, "--output", "json"]
61+
LOGGER.info(f"Listing tunnel with {list_cmd}")
62+
cloudflared = subprocess.run(list_cmd, check=True, capture_output=True)
63+
return json.loads(cloudflared.stdout)[0]["id"]
64+
65+
66+
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
67+
def delete_tunnel(config):
68+
credentials_path = config["credentials_file"]
69+
delete_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", config["origincert"], "delete",
70+
"--credentials-file", credentials_path, "-f", config["tunnel"]]
71+
LOGGER.info(f"Deleting tunnel with {delete_cmd}")
72+
subprocess.run(delete_cmd, check=True)
73+
74+
75+
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
76+
def create_dns(config, hostname, type, content):
77+
cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN"))
78+
cf.zones.dns_records.post(
79+
config["zone_tag"],
80+
data={'name': hostname, 'type': type, 'content': content, 'proxied': True}
81+
)
82+
83+
84+
def create_classic_dns(config, random_uuid):
85+
classic_hostname = "classic-" + random_uuid + "." + config["zone_domain"]
86+
create_dns(config, classic_hostname, "AAAA", "fd10:aec2:5dae::")
87+
return classic_hostname
88+
89+
90+
def create_named_dns(config, random_uuid):
91+
hostname = "named-" + random_uuid + "." + config["zone_domain"]
92+
create_dns(config, hostname, "CNAME", config["tunnel"] + ".cfargotunnel.com")
93+
return hostname
94+
95+
96+
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
97+
def delete_dns(config, hostname):
98+
cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN"))
99+
zone_tag = config["zone_tag"]
100+
dns_records = cf.zones.dns_records.get(zone_tag, params={'name': hostname})
101+
if len(dns_records) > 0:
102+
cf.zones.dns_records.delete(zone_tag, dns_records[0]['id'])
103+
104+
105+
def write_file(content, path):
106+
with open(path, 'w') as outfile:
107+
outfile.write(content)
108+
109+
110+
def get_env(env_name):
111+
val = os.getenv(env_name)
112+
if val is None:
113+
raise Exception(f"{env_name} is not set")
114+
return val
115+
116+
117+
def create():
118+
"""
119+
Creates the necessary resources for the components test to run.
120+
- Creates a named tunnel with a random name.
121+
- Creates a random CNAME DNS entry for that tunnel.
122+
- Creates a random AAAA DNS entry for a classic tunnel.
123+
124+
Those created resources are added to the config (obtained from an environment variable).
125+
The resulting configuration is persisted for the tests to use.
126+
"""
127+
config = get_config_from_env()
128+
origincert_path = persist_origin_cert(config)
129+
130+
random_uuid = str(uuid.uuid4())
131+
config["tunnel"] = create_tunnel(config, origincert_path, random_uuid)
132+
config["classic_hostname"] = create_classic_dns(config, random_uuid)
133+
config["ingress"] = [
134+
{
135+
"hostname": create_named_dns(config, random_uuid),
136+
"service": "hello_world"
137+
},
138+
{
139+
"service": "http_status:404"
140+
}
141+
]
142+
143+
persist_config(config)
144+
145+
146+
def cleanup():
147+
"""
148+
Reads the persisted configuration that was created previously.
149+
Deletes the resources that were created there.
150+
"""
151+
config = get_config_from_file()
152+
delete_tunnel(config)
153+
delete_dns(config, config["classic_hostname"])
154+
delete_dns(config, config["ingress"][0]["hostname"])
155+
156+
157+
if __name__ == '__main__':
158+
parser = argparse.ArgumentParser(description='setup component tests')
159+
parser.add_argument('--type', choices=['create', 'cleanup'], default='create')
160+
args = parser.parse_args()
161+
162+
if args.type == 'create':
163+
create()
164+
else:
165+
cleanup()

component-tests/test_logging.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env python
22
import json
33
import os
4-
import subprocess
54

65
from util import start_cloudflared, wait_tunnel_ready, send_requests
76

0 commit comments

Comments
 (0)