blob: 75cf8f3427769de83b0579b15502a8a66026aed2 [file] [log] [blame]
James E. Blair4f568262017-12-21 09:18:21 -08001#!/usr/bin/env python
2
3# Copyright 2014 Hewlett-Packard Development Company, L.P.
4# Copyright 2014 Rackspace Australia
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import asyncio
19import threading
20import os
21import json
22import urllib
23import time
24import socket
James E. Blair4f568262017-12-21 09:18:21 -080025
26import zuul.web
27
28from tests.base import ZuulTestCase, FIXTURE_DIR
29
30
Monty Taylor518dcf82018-01-23 12:51:26 -060031class FakeConfig(object):
32
33 def __init__(self, config):
34 self.config = config or {}
35
36 def has_option(self, section, option):
37 return option in self.config.get(section, {})
38
39 def get(self, section, option):
40 return self.config.get(section, {}).get(option)
41
42
43class BaseTestWeb(ZuulTestCase):
James E. Blair4f568262017-12-21 09:18:21 -080044 tenant_config_file = 'config/single-tenant/main.yaml'
Monty Taylor518dcf82018-01-23 12:51:26 -060045 config_ini_data = {}
James E. Blair4f568262017-12-21 09:18:21 -080046
47 def setUp(self):
Monty Taylor518dcf82018-01-23 12:51:26 -060048 super(BaseTestWeb, self).setUp()
James E. Blair4f568262017-12-21 09:18:21 -080049 self.executor_server.hold_jobs_in_build = True
50 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
51 A.addApproval('Code-Review', 2)
52 self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
53 B = self.fake_gerrit.addFakeChange('org/project1', 'master', 'B')
54 B.addApproval('Code-Review', 2)
55 self.fake_gerrit.addEvent(B.addApproval('Approved', 1))
56 self.waitUntilSettled()
57
Monty Taylor518dcf82018-01-23 12:51:26 -060058 self.zuul_ini_config = FakeConfig(self.config_ini_data)
James E. Blair4f568262017-12-21 09:18:21 -080059 # Start the web server
60 self.web = zuul.web.ZuulWeb(
61 listen_address='127.0.0.1', listen_port=0,
Monty Taylor518dcf82018-01-23 12:51:26 -060062 gear_server='127.0.0.1', gear_port=self.gearman_server.port,
63 info=zuul.model.WebInfo.fromConfig(self.zuul_ini_config)
64 )
James E. Blair4f568262017-12-21 09:18:21 -080065 loop = asyncio.new_event_loop()
66 loop.set_debug(True)
67 ws_thread = threading.Thread(target=self.web.run, args=(loop,))
68 ws_thread.start()
69 self.addCleanup(loop.close)
70 self.addCleanup(ws_thread.join)
71 self.addCleanup(self.web.stop)
72
73 self.host = 'localhost'
74 # Wait until web server is started
75 while True:
76 time.sleep(0.1)
77 if self.web.server is None:
78 continue
79 self.port = self.web.server.sockets[0].getsockname()[1]
80 print(self.host, self.port)
81 try:
82 with socket.create_connection((self.host, self.port)):
83 break
84 except ConnectionRefusedError:
85 pass
86
87 def tearDown(self):
88 self.executor_server.hold_jobs_in_build = False
89 self.executor_server.release()
90 self.waitUntilSettled()
Monty Taylor518dcf82018-01-23 12:51:26 -060091 super(BaseTestWeb, self).tearDown()
92
93
94class TestWeb(BaseTestWeb):
James E. Blair4f568262017-12-21 09:18:21 -080095
96 def test_web_status(self):
Tobias Henkele0bad8d2018-01-23 12:34:15 +010097 "Test that we can retrieve JSON status info"
98 self.executor_server.hold_jobs_in_build = True
99 A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
100 A.addApproval('Code-Review', 2)
101 self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
102 self.waitUntilSettled()
103
104 self.executor_server.release('project-merge')
105 self.waitUntilSettled()
James E. Blair4f568262017-12-21 09:18:21 -0800106
107 req = urllib.request.Request(
Monty Taylor9010dc52018-02-17 13:29:28 -0600108 "http://localhost:%s/tenant-one/status" % self.port)
James E. Blair4f568262017-12-21 09:18:21 -0800109 f = urllib.request.urlopen(req)
Tobias Henkele0bad8d2018-01-23 12:34:15 +0100110 headers = f.info()
111 self.assertIn('Content-Length', headers)
112 self.assertIn('Content-Type', headers)
113 self.assertEqual(
114 'application/json; charset=utf-8', headers['Content-Type'])
115 self.assertIn('Access-Control-Allow-Origin', headers)
116 self.assertIn('Cache-Control', headers)
117 self.assertIn('Last-Modified', headers)
118 data = f.read().decode('utf8')
James E. Blair4f568262017-12-21 09:18:21 -0800119
Tobias Henkele0bad8d2018-01-23 12:34:15 +0100120 self.executor_server.hold_jobs_in_build = False
121 self.executor_server.release()
122 self.waitUntilSettled()
123
124 data = json.loads(data)
125 status_jobs = []
126 for p in data['pipelines']:
127 for q in p['change_queues']:
128 if p['name'] in ['gate', 'conflict']:
129 self.assertEqual(q['window'], 20)
130 else:
131 self.assertEqual(q['window'], 0)
132 for head in q['heads']:
133 for change in head:
134 self.assertTrue(change['active'])
135 self.assertIn(change['id'], ('1,1', '2,1', '3,1'))
136 for job in change['jobs']:
137 status_jobs.append(job)
138 self.assertEqual('project-merge', status_jobs[0]['name'])
139 # TODO(mordred) pull uuids from self.builds
140 self.assertEqual(
141 'stream.html?uuid={uuid}&logfile=console.log'.format(
142 uuid=status_jobs[0]['uuid']),
143 status_jobs[0]['url'])
144 self.assertEqual(
145 'finger://{hostname}/{uuid}'.format(
146 hostname=self.executor_server.hostname,
147 uuid=status_jobs[0]['uuid']),
148 status_jobs[0]['finger_url'])
149 # TOOD(mordred) configure a success-url on the base job
150 self.assertEqual(
151 'finger://{hostname}/{uuid}'.format(
152 hostname=self.executor_server.hostname,
153 uuid=status_jobs[0]['uuid']),
154 status_jobs[0]['report_url'])
155 self.assertEqual('project-test1', status_jobs[1]['name'])
156 self.assertEqual(
157 'stream.html?uuid={uuid}&logfile=console.log'.format(
158 uuid=status_jobs[1]['uuid']),
159 status_jobs[1]['url'])
160 self.assertEqual(
161 'finger://{hostname}/{uuid}'.format(
162 hostname=self.executor_server.hostname,
163 uuid=status_jobs[1]['uuid']),
164 status_jobs[1]['finger_url'])
165 self.assertEqual(
166 'finger://{hostname}/{uuid}'.format(
167 hostname=self.executor_server.hostname,
168 uuid=status_jobs[1]['uuid']),
169 status_jobs[1]['report_url'])
170
171 self.assertEqual('project-test2', status_jobs[2]['name'])
172 self.assertEqual(
173 'stream.html?uuid={uuid}&logfile=console.log'.format(
174 uuid=status_jobs[2]['uuid']),
175 status_jobs[2]['url'])
176 self.assertEqual(
177 'finger://{hostname}/{uuid}'.format(
178 hostname=self.executor_server.hostname,
179 uuid=status_jobs[2]['uuid']),
180 status_jobs[2]['finger_url'])
181 self.assertEqual(
182 'finger://{hostname}/{uuid}'.format(
183 hostname=self.executor_server.hostname,
184 uuid=status_jobs[2]['uuid']),
185 status_jobs[2]['report_url'])
186
187 # check job dependencies
188 self.assertIsNotNone(status_jobs[0]['dependencies'])
189 self.assertIsNotNone(status_jobs[1]['dependencies'])
190 self.assertIsNotNone(status_jobs[2]['dependencies'])
191 self.assertEqual(len(status_jobs[0]['dependencies']), 0)
192 self.assertEqual(len(status_jobs[1]['dependencies']), 1)
193 self.assertEqual(len(status_jobs[2]['dependencies']), 1)
194 self.assertIn('project-merge', status_jobs[1]['dependencies'])
195 self.assertIn('project-merge', status_jobs[2]['dependencies'])
James E. Blair4f568262017-12-21 09:18:21 -0800196
197 def test_web_bad_url(self):
198 # do we 404 correctly
199 req = urllib.request.Request(
200 "http://localhost:%s/status/foo" % self.port)
201 self.assertRaises(urllib.error.HTTPError, urllib.request.urlopen, req)
202
James E. Blair4f568262017-12-21 09:18:21 -0800203 def test_web_find_change(self):
204 # can we filter by change id
205 req = urllib.request.Request(
206 "http://localhost:%s/tenant-one/status/change/1,1" % self.port)
207 f = urllib.request.urlopen(req)
208 data = json.loads(f.read().decode('utf8'))
209
210 self.assertEqual(1, len(data), data)
211 self.assertEqual("org/project", data[0]['project'])
212
213 req = urllib.request.Request(
214 "http://localhost:%s/tenant-one/status/change/2,1" % self.port)
215 f = urllib.request.urlopen(req)
216 data = json.loads(f.read().decode('utf8'))
217
218 self.assertEqual(1, len(data), data)
219 self.assertEqual("org/project1", data[0]['project'], data)
220
221 def test_web_keys(self):
222 with open(os.path.join(FIXTURE_DIR, 'public.pem'), 'rb') as f:
223 public_pem = f.read()
224
225 req = urllib.request.Request(
226 "http://localhost:%s/tenant-one/org/project.pub" %
227 self.port)
228 f = urllib.request.urlopen(req)
229 self.assertEqual(f.read(), public_pem)
230
James E. Blair4f568262017-12-21 09:18:21 -0800231 def test_web_404_on_unknown_tenant(self):
232 req = urllib.request.Request(
Monty Taylor9010dc52018-02-17 13:29:28 -0600233 "http://localhost:{}/non-tenant/status".format(self.port))
James E. Blair4f568262017-12-21 09:18:21 -0800234 e = self.assertRaises(
235 urllib.error.HTTPError, urllib.request.urlopen, req)
236 self.assertEqual(404, e.code)
Monty Taylor518dcf82018-01-23 12:51:26 -0600237
238
239class TestInfo(BaseTestWeb):
240
241 def setUp(self):
242 super(TestInfo, self).setUp()
243 web_config = self.config_ini_data.get('web', {})
244 self.websocket_url = web_config.get('websocket_url')
245 self.stats_url = web_config.get('stats_url')
246 statsd_config = self.config_ini_data.get('statsd', {})
247 self.stats_prefix = statsd_config.get('prefix')
248
249 def test_info(self):
250 req = urllib.request.Request(
251 "http://localhost:%s/info" % self.port)
252 f = urllib.request.urlopen(req)
253 info = json.loads(f.read().decode('utf8'))
254 self.assertEqual(
255 info, {
256 "info": {
Monty Taylorf93c2fb2018-02-20 14:38:47 -0600257 "rest_api_url": None,
Monty Taylor518dcf82018-01-23 12:51:26 -0600258 "capabilities": {
259 "job_history": False
260 },
261 "stats": {
262 "url": self.stats_url,
263 "prefix": self.stats_prefix,
264 "type": "graphite",
265 },
266 "websocket_url": self.websocket_url,
267 }
268 })
269
270 def test_tenant_info(self):
271 req = urllib.request.Request(
272 "http://localhost:%s/tenant-one/info" % self.port)
273 f = urllib.request.urlopen(req)
274 info = json.loads(f.read().decode('utf8'))
275 self.assertEqual(
276 info, {
277 "info": {
Monty Taylorf93c2fb2018-02-20 14:38:47 -0600278 "rest_api_url": None,
Monty Taylor518dcf82018-01-23 12:51:26 -0600279 "tenant": "tenant-one",
280 "capabilities": {
281 "job_history": False
282 },
283 "stats": {
284 "url": self.stats_url,
285 "prefix": self.stats_prefix,
286 "type": "graphite",
287 },
288 "websocket_url": self.websocket_url,
289 }
290 })
291
292
293class TestWebSocketInfo(TestInfo):
294
295 config_ini_data = {
296 'web': {
297 'websocket_url': 'wss://ws.example.com'
298 }
299 }
300
301
302class TestGraphiteUrl(TestInfo):
303
304 config_ini_data = {
305 'statsd': {
306 'prefix': 'example'
307 },
308 'web': {
309 'stats_url': 'https://graphite.example.com',
310 }
311 }