Skip to content

Commit e3caf2f

Browse files
committed
Add comments and other minor refactors.
1 parent db54b8c commit e3caf2f

File tree

4 files changed

+140
-94
lines changed

4 files changed

+140
-94
lines changed

test/cluster/keytar/keytar.py

Lines changed: 93 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#!/usr/bin/env python
2-
"""Keytar flask app."""
2+
"""Keytar flask app.
3+
4+
This program is responsible for exposing an interface to trigger cluster level
5+
tests. For instance, docker webhooks can be configured to point to this
6+
application in order to trigger tests upon pushing new docker images.
7+
"""
38

49
import argparse
510
import datetime
@@ -22,12 +27,16 @@
2227
results = {}
2328

2429

30+
class KeytarError(Exception):
31+
pass
32+
33+
2534
def run_test_config(config):
2635
"""Runs a single test iteration from a configuration."""
2736
tempdir = tempfile.mkdtemp()
2837
logging.info('Fetching github repository')
2938

30-
# Get the github repo
39+
# Get the github repo and clone it.
3140
github_config = config['github']
3241
github_clone_args, github_repo_dir = _get_download_github_repo_args(
3342
tempdir, github_config)
@@ -40,7 +49,8 @@ def run_test_config(config):
4049
_add_new_result(timestamp)
4150
results[timestamp]['docker_image'] = config['docker_image']
4251

43-
# Run initialization
52+
# Generate a test script with the steps described in the configuration,
53+
# as well as the command to execute the test_runner.
4454
with tempfile.NamedTemporaryFile(dir=tempdir, delete=False) as f:
4555
tempscript = f.name
4656
f.write('#!/bin/bash\n')
@@ -71,6 +81,7 @@ def index():
7181

7282
@app.route('/test_results')
7383
def test_results():
84+
# Get all test results or a specific test result in JSON.
7485
if 'name' not in flask.request.args:
7586
return json.dumps([results[x] for x in sorted(results)])
7687
else:
@@ -80,13 +91,15 @@ def test_results():
8091

8192
@app.route('/test_log')
8293
def test_log():
94+
# Fetch the output from a test.
8395
log = '%s.log' % os.path.basename(flask.request.values['log_name'])
8496
return (flask.send_from_directory('/tmp/testlogs', log), 200,
8597
{'Content-Type': 'text/css'})
8698

8799

88100
@app.route('/update_results', methods=['POST'])
89101
def update_results():
102+
# Update the results dict, called from the test_runner.
90103
update_args = flask.request.get_json()
91104
time = update_args['time']
92105
for k, v in update_args.iteritems():
@@ -119,59 +132,74 @@ def test_request():
119132
flask.abort(400, validation_error)
120133
webhook_data = flask.request.get_json()
121134
repo_name = webhook_data['repository']['repo_name']
122-
configs = [c for c in keytar_config if c['docker_image'] == repo_name]
123-
if not configs:
135+
test_configs = [
136+
c for c in keytar_config['config'] if c['docker_image'] == repo_name]
137+
if not test_configs:
124138
return 'No config found for repo_name: %s' % repo_name
125-
for config in configs:
126-
test_worker.add_test(config)
139+
for test_config in test_configs:
140+
test_worker.add_test(test_config)
127141
return 'OK'
128142

129143

130-
def process_config(config):
131-
global keytar_config
132-
keytar_config = config['config']
133-
if 'install' in config:
134-
install_config = config['install']
135-
if 'cluster_setup' in install_config:
136-
for cluster_setup in install_config['cluster_setup']:
137-
if cluster_setup['type'] == 'gke':
138-
gcloud_args = ['gcloud', 'auth', 'activate-service-account',
139-
'--key-file', cluster_setup['keyfile']]
140-
logging.info('authenticating using keyfile: %s',
141-
cluster_setup['keyfile'])
142-
subprocess.call(gcloud_args)
143-
if 'project_name' in cluster_setup:
144-
logging.info('Setting gcloud project to %s',
145-
cluster_setup['project_name'])
146-
subprocess.call(
147-
['gcloud', 'config', 'set', 'project',
148-
cluster_setup['project_name']])
149-
else:
150-
config = subprocess.check_output(
151-
['gcloud', 'config', 'list', '--format', 'json'])
152-
project_name = json.loads(config)['core']['project']
153-
if not project_name:
154-
projects = subprocess.check_output(
155-
['gcloud', 'projects', 'list'])
156-
first_project = projects[0]['projectId']
157-
logging.info('gcloud project is unset, setting it to %s',
158-
first_project)
159-
subprocess.check_output(
160-
['gcloud', 'config', 'set', 'project', first_project])
161-
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = (
162-
cluster_setup['keyfile'])
163-
if 'dependencies' in install_config:
164-
subprocess.call(['apt-get', 'update'])
165-
os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
166-
for dep in install_config['dependencies']:
167-
subprocess.call(
168-
['apt-get', 'install', '-y', '--no-install-recommends', dep])
169-
if 'extra' in install_config:
170-
for step in install_config['extra']:
171-
os.system(step)
172-
if 'path' in install_config:
173-
for path in install_config['path']:
174-
os.environ['PATH'] = '%s:%s' % (path, os.environ['PATH'])
144+
def handle_install_steps():
145+
"""Runs all config installation/setup steps."""
146+
if 'install' not in keytar_config:
147+
return
148+
install_config = keytar_config['install']
149+
if 'cluster_setup' in install_config:
150+
# Handle any cluster setup steps, currently only GKE is supported.
151+
for cluster_setup in install_config['cluster_setup']:
152+
if cluster_setup['type'] == 'gke':
153+
if 'keyfile' not in cluster_setup:
154+
raise KeytarError('No keyfile found in GKE cluster setup!')
155+
# Add authentication steps to allow keytar to start clusters on GKE.
156+
gcloud_args = ['gcloud', 'auth', 'activate-service-account',
157+
'--key-file', cluster_setup['keyfile']]
158+
logging.info('authenticating using keyfile: %s',
159+
cluster_setup['keyfile'])
160+
subprocess.call(gcloud_args)
161+
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = (
162+
cluster_setup['keyfile'])
163+
164+
# Ensure that a project name is correctly set. Use the name if provided
165+
# in the configuration, otherwise use the current project name, or else
166+
# the first available project name.
167+
if 'project_name' in cluster_setup:
168+
logging.info('Setting gcloud project to %s',
169+
cluster_setup['project_name'])
170+
subprocess.call(
171+
['gcloud', 'config', 'set', 'project',
172+
cluster_setup['project_name']])
173+
else:
174+
config = subprocess.check_output(
175+
['gcloud', 'config', 'list', '--format', 'json'])
176+
project_name = json.loads(config)['core']['project']
177+
if not project_name:
178+
projects = subprocess.check_output(
179+
['gcloud', 'projects', 'list'])
180+
first_project = projects[0]['projectId']
181+
logging.info('gcloud project is unset, setting it to %s',
182+
first_project)
183+
subprocess.check_output(
184+
['gcloud', 'config', 'set', 'project', first_project])
185+
186+
# Install any dependencies using apt-get.
187+
if 'dependencies' in install_config:
188+
subprocess.call(['apt-get', 'update'])
189+
os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
190+
for dep in install_config['dependencies']:
191+
subprocess.call(
192+
['apt-get', 'install', '-y', '--no-install-recommends', dep])
193+
194+
# Run any additional commands if provided.
195+
if 'extra' in install_config:
196+
for step in install_config['extra']:
197+
os.system(step)
198+
199+
# Update path environment variable.
200+
if 'path' in install_config:
201+
for path in install_config['path']:
202+
os.environ['PATH'] = '%s:%s' % (path, os.environ['PATH'])
175203

176204

177205
def _add_new_result(timestamp):
@@ -180,6 +208,16 @@ def _add_new_result(timestamp):
180208

181209

182210
def _get_download_github_repo_args(tempdir, github_config):
211+
"""Get arguments for github actions.
212+
213+
Args:
214+
tempdir: Base directory to git clone into.
215+
github_config: Configuration describing the repo, branches, etc.
216+
217+
Returns:
218+
([string], string) for arguments to pass to git, and the directory to
219+
clone into.
220+
"""
183221
repo_prefix = 'github'
184222
if 'repo_prefix' in github_config:
185223
repo_prefix = github_config['repo_prefix']
@@ -192,9 +230,9 @@ def _get_download_github_repo_args(tempdir, github_config):
192230

193231

194232
class TestWorker(object):
233+
"""A simple test queue. HTTP requests append to this work queue."""
195234

196235
def __init__(self):
197-
# Create a simple test queue. HTTP requests simply append to the queue.
198236
self.test_queue = Queue.Queue()
199237
self.worker_thread = threading.Thread(target=self.worker_loop)
200238
self.worker_thread.daemon = True
@@ -226,7 +264,8 @@ def add_test(self, config):
226264
yaml_config = yaml_file.read()
227265
if not yaml_config:
228266
raise ValueError('No valid yaml config!')
229-
process_config(yaml.load(yaml_config))
267+
keytar_config = yaml.load(yaml_config)
268+
handle_install_steps()
230269

231270
if not os.path.isdir('/tmp/testlogs'):
232271
os.mkdir('/tmp/testlogs')

test/cluster/keytar/keytar_web_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def setUpClass(cls):
4343
cls.driver = create_webdriver()
4444
cls.flask_process = subprocess.Popen(
4545
['./keytar.py', '--config_file=test_config.yaml',
46-
'--port=8080', '--key=foo'],
46+
'--port=8080', '--password=foo'],
4747
preexec_fn=os.setsid)
4848

4949
@classmethod
@@ -63,7 +63,7 @@ def _wait_for_complete_status(self, timeout_s=180):
6363

6464
def test_keytar_web(self):
6565
self.driver.get('http://localhost:8080')
66-
req = urllib2.Request('http://localhost:8080/test_request?key=foo')
66+
req = urllib2.Request('http://localhost:8080/test_request?password=foo')
6767
req.add_header('Content-Type', 'application/json')
6868
urllib2.urlopen(
6969
req, json.dumps({'repository': {'repo_name': 'test/image'}}))

test/cluster/keytar/static/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ $(document).ready(function() {
2929
});
3030
html += "</tbody></table>";
3131
resultsElement.append(html);
32-
}
32+
};
3333

3434
// Poll every second.
3535
var fetchTestResults = function() {

test/cluster/keytar/test_runner.py

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
"""Main python file."""
1+
"""Script to run a single cluster test.
2+
3+
This includes the following steps:
4+
1. Starting a test cluster (GKE supported).
5+
2. Running tests against the cluster.
6+
3. Reporting test results.
7+
"""
28

39
import argparse
410
import json
@@ -13,46 +19,56 @@
1319

1420

1521
def update_result(k, v):
22+
"""Post a key/value pair test result update."""
1623
url = '%s/update_results' % keytar_args.server
1724
req = urllib2.Request(url)
1825
req.add_header('Content-Type', 'application/json')
1926
urllib2.urlopen(req, json.dumps({k: v, 'time': keytar_args.timestamp}))
2027

2128

22-
def start_sandbox(environment_config, name):
29+
def run_sandbox_action(environment_config, name, action):
30+
"""Run a sandbox action (Start/Stop).
31+
32+
Args:
33+
environment_config: yaml configuration for the sandbox.
34+
name: unique name for the sandbox.
35+
action: action to pass to the sandbox action parameter.
36+
"""
37+
if 'sandbox' not in environment_config:
38+
return
39+
# Execute sandbox command
40+
sandbox_file = os.path.join(repo_dir, environment_config['sandbox'])
41+
os.chdir(os.path.dirname(sandbox_file))
42+
sandbox_args = [
43+
'./%s' % os.path.basename(sandbox_file),
44+
'-e', environment_config['cluster_type'], '-n', name, '-k', name,
45+
'-c', os.path.join(repo_dir, environment_config['config']),
46+
'-a', action]
47+
update_result('status', 'Running sandbox action: %s' % action)
2348
try:
24-
# Start a sandbox
25-
sandbox_file = os.path.join(repo_dir, environment_config['sandbox'])
26-
os.chdir(os.path.dirname(sandbox_file))
27-
sandbox_args = [
28-
'./%s' % os.path.basename(sandbox_file),
29-
'-e', environment_config['cluster_type'], '-n', name, '-k', name,
30-
'-c', os.path.join(repo_dir, environment_config['config'])]
31-
update_result('status', 'Starting Sandbox')
32-
try:
33-
subprocess.check_call(sandbox_args + ['-a', 'Start'])
34-
except subprocess.CalledProcessError as e:
35-
logging.info('Failed to start sandbox: %s', e.output)
36-
update_result('status', 'Sandbox failure')
37-
38-
logging.info('Running tests')
39-
update_result('status', 'Running Tests')
40-
except Exception as e: # pylint: disable=broad-except
41-
logging.info('Exception caught: %s', str(e))
42-
update_result('status', 'System Error')
49+
subprocess.check_call(sandbox_args)
50+
except subprocess.CalledProcessError as e:
51+
logging.info('Failed to run sandbox action %s: %s', (action, e.output))
52+
update_result('status', 'Sandbox failure')
4353

4454

4555
def run_test_config():
46-
"""Runs a single test iteration from a configuration."""
56+
"""Runs a single test iteration from a configuration.
57+
58+
This includes bringing up an environment, running the tests, and reporting
59+
status.
60+
"""
61+
# Generate a random name. Kubernetes/GKE has name length limits.
4762
name = 'keytar%s' % format(uuid.uuid4().fields[0], 'x')
4863
update_result('name', name)
4964

5065
environment_config = config['environment']
51-
if 'sandbox' in environment_config:
52-
start_sandbox(environment_config, name)
66+
run_sandbox_action(environment_config, name, 'Start')
67+
logging.info('Running tests')
68+
update_result('status', 'Running Tests')
5369

5470
try:
55-
# Run tests
71+
# Run tests and update results.
5672
test_results = {}
5773
for test in config['tests']:
5874
test_file = os.path.join(repo_dir, test['file'])
@@ -78,24 +94,15 @@ def run_test_config():
7894
update_result('status', 'Tests Complete')
7995
except Exception as e: # pylint: disable=broad-except
8096
logging.info('Exception caught: %s', str(e))
81-
update_result('status', 'System Error')
97+
update_result('status', 'System Error running tests: %s' % str(e))
8298
finally:
83-
if 'sandbox' in environment_config:
84-
sandbox_file = os.path.join(repo_dir, environment_config['sandbox'])
85-
sandbox_args = [
86-
'./%s' % os.path.basename(sandbox_file),
87-
'-e', environment_config['cluster_type'], '-n', name, '-k', name,
88-
'-c', os.path.join(repo_dir, environment_config['config'])]
89-
os.chdir(os.path.dirname(sandbox_file))
90-
update_result('status', 'Teardown')
91-
subprocess.call(sandbox_args + ['-a', 'Stop'])
92-
update_result('status', 'Complete')
99+
run_sandbox_action(environment_config, name, 'Stop')
93100

94101

95102
if __name__ == '__main__':
96103
logging.getLogger().setLevel(logging.INFO)
97104
parser = argparse.ArgumentParser(description='Run keytar')
98-
parser.add_argument('-c', '--config', help='Keytar config json')
105+
parser.add_argument('-c', '--config', help='Keytar config yaml')
99106
parser.add_argument('-t', '--timestamp', help='Timestamp string')
100107
parser.add_argument('-d', '--dir', help='temp dir created for the test')
101108
parser.add_argument('-s', '--server', help='keytar server address')

0 commit comments

Comments
 (0)