Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(281)

Side by Side Diff: tests/20-functional.test

Issue 34130043: Deploy the GUI to machine 0 in functional tests.
Patch Set: Created 10 years, 4 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « revision ('k') | tests/deploy.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!tests/.venv/bin/python 1 #!tests/.venv/bin/python
2 2
3 # This file is part of the Juju GUI, which lets users view and manage Juju 3 # This file is part of the Juju GUI, which lets users view and manage Juju
4 # environments within a graphical interface (https://launchpad.net/juju-gui). 4 # environments within a graphical interface (https://launchpad.net/juju-gui).
5 # Copyright (C) 2012-2013 Canonical Ltd. 5 # Copyright (C) 2012-2013 Canonical Ltd.
6 # 6 #
7 # This program is free software: you can redistribute it and/or modify it under 7 # This program is free software: you can redistribute it and/or modify it under
8 # the terms of the GNU Affero General Public License version 3, as published by 8 # the terms of the GNU Affero General Public License version 3, as published by
9 # the Free Software Foundation. 9 # the Free Software Foundation.
10 # 10 #
11 # This program is distributed in the hope that it will be useful, but WITHOUT 11 # This program is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, 12 # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
13 # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Affero General Public License for more details. 14 # Affero General Public License for more details.
15 # 15 #
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18
19 from __future__ import print_function 19 from __future__ import print_function
20 import httplib 20 import httplib
21 import json
21 import itertools 22 import itertools
22 import unittest 23 import unittest
24 import urllib2
23 import urlparse 25 import urlparse
24 26
25 from selenium.webdriver import Firefox 27 from selenium.webdriver import Firefox
26 from selenium.webdriver.support import ui 28 from selenium.webdriver.support import ui
27 from xvfbwrapper import Xvfb 29 from xvfbwrapper import Xvfb
28 import yaml 30 import yaml
29 31
30 # XXX 2013-07-30 benji bug=872264: Don't use juju_deploy directly, use 32 # XXX 2013-07-30 benji bug=872264: Don't use juju_deploy directly, use
31 # DeployTestMixin.juju_deploy instead. See comment in the method. 33 # DeployTestMixin.juju_deploy instead. See comment in the method.
32 from deploy import juju_deploy 34 from deploy import juju_deploy
33 from helpers import ( 35 from helpers import (
34 get_admin_secret, 36 get_admin_secret,
35 juju_destroy_service, 37 juju_destroy_service,
36 juju_version, 38 juju_version,
39 make_service_name,
37 stop_services, 40 stop_services,
38 WebSocketClient, 41 WebSocketClient,
39 ) 42 )
40 import example 43 import example
41 44
42 JUJU_GUI_TEST_BRANCH = 'lp:~juju-gui/juju-gui/charm-tests-branch' 45 JUJU_GUI_TEST_BRANCH = 'lp:~juju-gui/juju-gui/charm-tests-branch'
43 STAGING_SERVICES = ('haproxy', 'mediawiki', 'memcached', 'mysql', 'wordpress') 46 STAGING_SERVICES = ('haproxy', 'mediawiki', 'memcached', 'mysql', 'wordpress')
44 is_legacy_juju = juju_version().major == 0 47 is_legacy_juju = juju_version().major == 0
45 try: 48 try:
46 admin_secret = get_admin_secret() 49 admin_secret = get_admin_secret()
47 except ValueError as err: 50 except ValueError as err:
48 admin_secret = None 51 admin_secret = None
49 print(err) 52 print(err)
50 53
51 54
55 def juju_deploy_gui(options=None):
56 """Deploy the Juju GUI charm with the given options.
57
58 Deploy the charm in the bootstrap node if possible in the current Juju
gary.poster 2013/11/27 17:57:09 Suggest slight tweak for readability: Deploy the
59 implementation. Use a random service name.
60
61 Return a tuple containing the deployed service name, unit info and a list
62 of services that are started in the Juju GUI unit and that must be
63 manually stopped by tests. In juju-core, the services list is always empty.
64 """
65 force_machine = None if is_legacy_juju else 0
66 service_name = make_service_name(prefix='juju-gui-')
67 unit_info = juju_deploy(
68 'juju-gui', service_name=service_name, options=options,
69 force_machine=force_machine)
70 # XXX 2012-11-29 frankban bug=872264:
71 # Just invoking ``juju destroy-service juju-gui`` in tearDown
72 # should execute the ``stop`` hook, stopping all the services
73 # started by the charm in the machine. Right now this does not
74 # work in pyJuju, so the desired effect is achieved by keeping
75 # track of started services and manually stopping them here.
76 # Once pyJuju works correctly or we drop support for it altogether, we
77 # can remove this shim.
78 cleanup_services = []
79 if is_legacy_juju:
80 if options is None:
81 options = {}
82 # Either stop the builtin server or the old apache2/haproxy setup.
83 if options.get('builtin-server') == 'true':
84 cleanup_services.append('guiserver')
85 else:
86 cleanup_services.extend(['haproxy', 'apache2'])
87 # Staging uses improv, otherwise the API agent is used.
88 if options.get('staging') == 'true':
89 cleanup_services.append('juju-api-improv')
90 else:
91 cleanup_services.append('juju-api-agent')
92 return service_name, unit_info, cleanup_services
93
94
52 class DeployTestMixin(object): 95 class DeployTestMixin(object):
53 96
54 charm = 'juju-gui'
55 port = '443' 97 port = '443'
56 98
57 def setUp(self): 99 def setUp(self):
58 # Perform all graphical operations in memory. 100 # Perform all graphical operations in memory.
59 vdisplay = Xvfb(width=1280, height=720) 101 vdisplay = Xvfb(width=1280, height=720)
60 vdisplay.start() 102 vdisplay.start()
61 self.addCleanup(vdisplay.stop) 103 self.addCleanup(vdisplay.stop)
62 # Create a Selenium browser instance. 104 # Create a Selenium browser instance.
63 selenium = self.selenium = Firefox() 105 selenium = self.selenium = Firefox()
64 self.addCleanup(selenium.quit) 106 self.addCleanup(selenium.quit)
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
128 condition = lambda driver: driver.execute_script(script) 170 condition = lambda driver: driver.execute_script(script)
129 return self.wait_for(condition, error=error, timeout=timeout) 171 return self.wait_for(condition, error=error, timeout=timeout)
130 172
131 def get_service_names(self): 173 def get_service_names(self):
132 """Return the set of services' names displayed in the current page.""" 174 """Return the set of services' names displayed in the current page."""
133 def services_found(driver): 175 def services_found(driver):
134 return driver.find_elements_by_css_selector('.service .name') 176 return driver.find_elements_by_css_selector('.service .name')
135 services = self.wait_for(services_found, 'Services not displayed.') 177 services = self.wait_for(services_found, 'Services not displayed.')
136 return set([element.text for element in services]) 178 return set([element.text for element in services])
137 179
138 def juju_deploy(self, *args, **kwargs): 180 def deploy_gui(self, options=None):
139 """Shim in our additional cleanup for pyJuju.""" 181 """Shim in our additional cleanup for pyJuju."""
140 # XXX 2012-11-29 frankban bug=872264:
141 # Just invoking ``juju destroy-service juju-gui`` in tearDown
142 # should execute the ``stop`` hook, stopping all the services
143 # started by the charm in the machine. Right now this does not
144 # work in pyJuju, so the desired effect is achieved by keeping
145 # track of started services and manually stopping them here.
146 # Once pyJuju works correctly or we drop support for it altogether, we 182 # Once pyJuju works correctly or we drop support for it altogether, we
147 # can remove this shim. 183 # can remove this shim.
148 unit_info, self.service_name = juju_deploy(*args, **kwargs) 184 self.service_name, unit_info, cleanup_services = juju_deploy_gui(
185 options=options)
186 # XXX 2013-11-27 frankban bug=872264:
187 # See juju_deploy_gui above.
149 if is_legacy_juju: 188 if is_legacy_juju:
150 hostname = unit_info['public-address'] 189 hostname = unit_info['public-address']
151 options = kwargs.get('options', {}) 190 self.addCleanup(stop_services, hostname, cleanup_services)
152 # Either stop the builtin server or the old apache2/haproxy setup.
153 if options.get('builtin-server') == 'true':
154 services = ['guiserver']
155 else:
156 services = ['haproxy', 'apache2']
157 # Staging uses improv, otherwise the API agent is used.
158 if options.get('staging') == 'true':
159 services.append('juju-api-improv')
160 else:
161 services.append('juju-api-agent')
162 self.addCleanup(stop_services, hostname, services)
163 return unit_info 191 return unit_info
164 192
193 def get_builtin_server_info(self, hostname):
194 """Return a dictionary of info as exposed by the builtin server."""
195 url = 'https://{}/gui-server-info'.format(hostname)
196 response = urllib2.urlopen(url)
197 self.assertEqual(200, response.code)
198 return json.load(response)
165 199
166 class TestDeploy(DeployTestMixin, unittest.TestCase): 200
201 class TestDeployOptions(DeployTestMixin, unittest.TestCase):
167 202
168 def tearDown(self): 203 def tearDown(self):
169 juju_destroy_service(self.service_name) 204 juju_destroy_service(self.service_name)
170 205
171 def test_local_release(self): 206 def test_stable_release(self):
172 # Ensure the Juju GUI and API agent services are correctly set up when 207 # Ensure the stable Juju GUI release is correctly set up.
173 # deploying the local release. 208 unit_info = self.deploy_gui(options={'juju-gui-source': 'stable'})
174 unit_info = self.juju_deploy(self.charm)
175 hostname = unit_info['public-address'] 209 hostname = unit_info['public-address']
176 self.navigate_to(hostname) 210 self.navigate_to(hostname)
177 self.handle_browser_warning() 211 self.handle_browser_warning()
178 self.assertEnvironmentIsConnected()
179
180 def test_stable_release(self):
181 # Ensure the Juju GUI and API agent services are correctly set up when
182 # deploying the stable release.
183 options = {'juju-gui-source': 'stable'}
184 unit_info = self.juju_deploy(self.charm, options=options)
185 hostname = unit_info['public-address']
186 self.navigate_to(hostname)
187 self.handle_browser_warning()
188 self.assertEnvironmentIsConnected() 212 self.assertEnvironmentIsConnected()
189 213
190 @unittest.skipUnless(is_legacy_juju, 'staging only works in pyJuju') 214 @unittest.skipUnless(is_legacy_juju, 'staging only works in pyJuju')
191 def test_staging(self): 215 def test_staging(self):
192 # Ensure the Juju GUI and improv services are correctly set up. 216 # Ensure the Juju GUI and staging services are correctly set up.
193 unit_info = self.juju_deploy(self.charm, options={'staging': 'true'}) 217 unit_info = self.deploy_gui(options={'staging': 'true'})
194 hostname = unit_info['public-address'] 218 hostname = unit_info['public-address']
195 self.navigate_to(hostname) 219 self.navigate_to(hostname)
196 self.handle_browser_warning() 220 self.handle_browser_warning()
197 self.assertEnvironmentIsConnected() 221 self.assertEnvironmentIsConnected()
198 # The staging environment contains five deployed services. 222 # The staging environment contains five deployed services.
199 self.assertSetEqual(set(STAGING_SERVICES), self.get_service_names()) 223 self.assertSetEqual(set(STAGING_SERVICES), self.get_service_names())
200 224
201 def test_sandbox(self): 225 def test_sandbox(self):
202 # The GUI is correctly deployed and set up in sandbox mode. 226 # The GUI is correctly deployed and set up in sandbox mode.
203 unit_info = self.juju_deploy(self.charm, options={'sandbox': 'true'}) 227 unit_info = self.deploy_gui(options={'sandbox': 'true'})
204 hostname = unit_info['public-address'] 228 hostname = unit_info['public-address']
205 self.navigate_to(hostname) 229 self.navigate_to(hostname)
206 self.handle_browser_warning() 230 self.handle_browser_warning()
207 self.assertEnvironmentIsConnected() 231 self.assertEnvironmentIsConnected()
232 # Ensure the builtin server is set up to run in sandbox mode.
233 server_info = self.get_builtin_server_info(hostname)
234 self.assertTrue(server_info['sandbox'])
208 235
209 def test_branch_source(self): 236 def test_branch_source(self):
210 # Ensure the Juju GUI is correctly deployed from a Bazaar branch. 237 # Ensure the Juju GUI is correctly deployed from a Bazaar branch.
211 options = {'juju-gui-source': JUJU_GUI_TEST_BRANCH} 238 options = {'juju-gui-source': JUJU_GUI_TEST_BRANCH}
212 unit_info = self.juju_deploy(self.charm, options=options) 239 unit_info = self.deploy_gui(options=options)
213 hostname = unit_info['public-address'] 240 hostname = unit_info['public-address']
214 self.navigate_to(hostname) 241 self.navigate_to(hostname)
215 self.handle_browser_warning() 242 self.handle_browser_warning()
216 self.assertEnvironmentIsConnected() 243 self.assertEnvironmentIsConnected()
217 244
218 def test_legacy_server(self): 245 def test_legacy_server(self):
219 # The legacy apache + haproxy server configuration works correctly. 246 # The legacy apache + haproxy server configuration works correctly.
220 # Also make sure the correct cache headers are sent. 247 # Also make sure the correct cache headers are sent.
221 options = { 248 unit_info = self.deploy_gui(options={'builtin-server': 'false'})
222 'builtin-server': False,
223 'juju-gui-source': JUJU_GUI_TEST_BRANCH,
224 }
225 unit_info = self.juju_deploy(self.charm, options=options)
226 hostname = unit_info['public-address'] 249 hostname = unit_info['public-address']
250 self.navigate_to(hostname)
251 self.handle_browser_warning()
252 self.assertEnvironmentIsConnected()
227 conn = httplib.HTTPSConnection(hostname) 253 conn = httplib.HTTPSConnection(hostname)
228 conn.request('HEAD', '/') 254 conn.request('HEAD', '/')
229 headers = conn.getresponse().getheaders() 255 headers = conn.getresponse().getheaders()
230 # There is only one Cache-Control header. 256 # There is only one Cache-Control header.
231 self.assertEqual(zip(*headers)[0].count('cache-control'), 1) 257 self.assertEqual(zip(*headers)[0].count('cache-control'), 1)
232 # The right cache directives are in Cache-Control. 258 # The right cache directives are in Cache-Control.
233 cache_control = dict(headers)['cache-control'] 259 cache_control = dict(headers)['cache-control']
234 cache_directives = [s.strip() for s in cache_control.split(',')] 260 cache_directives = [s.strip() for s in cache_control.split(',')]
235 self.assertIn('max-age=0', cache_directives) 261 self.assertIn('max-age=0', cache_directives)
236 self.assertIn('public', cache_directives) 262 self.assertIn('public', cache_directives)
237 self.assertIn('must-revalidate', cache_directives) 263 self.assertIn('must-revalidate', cache_directives)
238 264
239 @unittest.skipIf(is_legacy_juju, 'force-machine only works in juju-core')
240 def test_force_machine(self):
241 # Ensure the Juju GUI is correctly set up in the Juju bootstrap node.
242 unit_info = self.juju_deploy(self.charm, force_machine=0)
243 self.assertEqual('0', unit_info['machine'])
244 self.navigate_to(unit_info['public-address'])
245 self.handle_browser_warning()
246 self.assertEnvironmentIsConnected()
247 265
248 def test_nrpe_check_available(self): 266 class TestBuiltinServerLocalRelease(DeployTestMixin, unittest.TestCase):
249 # Make sure the check-app-access.sh script's ADDRESS is available.
250 options = {'juju-gui-source': JUJU_GUI_TEST_BRANCH}
251 unit_info = self.juju_deploy(self.charm, options=options)
252 hostname = unit_info['public-address']
253 conn = httplib.HTTPSConnection(hostname)
254 # This request matches the ADDRESS var in the script.
255 conn.request('GET', '/juju-ui/version.js')
256 message = 'ADDRESS in check-app-access.sh is not accessible.'
257 self.assertEqual(200, conn.getresponse().status, message)
258
259
260 class TestBuiltinServer(DeployTestMixin, unittest.TestCase):
261 267
262 @classmethod 268 @classmethod
263 def setUpClass(cls): 269 def setUpClass(cls):
264 # Deploy the charm. The resulting service is used by all the tests 270 # Deploy the charm. The resulting service is used by all the tests
265 # in this test case. 271 # in this test case.
266 unit_info, cls.service_name = juju_deploy(cls.charm) 272 cls.service_name, unit_info, cls.cleanup_services = juju_deploy_gui()
267 cls.hostname = unit_info['public-address'] 273 cls.hostname = unit_info['public-address']
268 # The counter is used to produce API request identifiers. 274 # The counter is used to produce API request identifiers.
269 cls.counter = itertools.count() 275 cls.counter = itertools.count()
270 276
271 @classmethod 277 @classmethod
272 def tearDownClass(cls): 278 def tearDownClass(cls):
273 # Destroy the GUI service, and perform additional clean up in the case 279 # Destroy the GUI service, and perform additional clean up in the case
274 # we are in a pyJuju environment. 280 # we are in a pyJuju environment.
275 juju_destroy_service(cls.service_name) 281 juju_destroy_service(cls.service_name)
282 # XXX 2013-11-27 frankban bug=872264:
283 # See juju_deploy_gui above.
276 if is_legacy_juju: 284 if is_legacy_juju:
277 # XXX 2012-11-29 frankban bug=872264: 285 stop_services(cls.hostname, cls.cleanup_services)
278 # see DeployTestMixin.juju_deploy above.
279 stop_services(cls.hostname, ['guiserver', 'juju-api-agent'])
280 286
281 def make_websocket_client(self, authenticated=True): 287 def make_websocket_client(self, authenticated=True):
282 """Create and return a WebSocket client connected to the Juju backend. 288 """Create and return a WebSocket client connected to the Juju backend.
283 289
284 If authenticated is set to True, also log in to the Juju API server. 290 If authenticated is set to True, also log in to the Juju API server.
285 """ 291 """
286 client = WebSocketClient('wss://{}:443/ws'.format(self.hostname)) 292 client = WebSocketClient('wss://{}:443/ws'.format(self.hostname))
287 client.connect() 293 client.connect()
288 self.addCleanup(client.close) 294 self.addCleanup(client.close)
289 if authenticated: 295 if authenticated:
290 response = client.send({ 296 response = client.send({
291 'RequestId': self.counter.next(), 297 'RequestId': self.counter.next(),
292 'Type': 'Admin', 298 'Type': 'Admin',
293 'Request': 'Login', 299 'Request': 'Login',
294 'Params': {'AuthTag': 'user-admin', 'Password': admin_secret}, 300 'Params': {'AuthTag': 'user-admin', 'Password': admin_secret},
295 }) 301 })
296 self.assertNotIn('Error', response) 302 self.assertNotIn('Error', response)
297 return client 303 return client
298 304
299 def test_environment_connection(self): 305 def test_environment_connection(self):
300 # Ensure the Juju GUI and builtin server are correctly set up. 306 # Ensure the Juju GUI and builtin server are correctly set up using
307 # the local release.
301 self.navigate_to(self.hostname) 308 self.navigate_to(self.hostname)
302 self.handle_browser_warning() 309 self.handle_browser_warning()
303 self.assertEnvironmentIsConnected() 310 self.assertEnvironmentIsConnected()
311 # Ensure the builtin server is set up to be connected to the real env.
312 server_info = self.get_builtin_server_info(self.hostname)
313 self.assertFalse(server_info['sandbox'])
304 314
305 def test_headers(self): 315 def test_headers(self):
306 # Ensure the Tornado headers are correctly sent. 316 # Ensure the Tornado headers are correctly sent.
307 conn = httplib.HTTPSConnection(self.hostname) 317 conn = httplib.HTTPSConnection(self.hostname)
308 conn.request('HEAD', '/') 318 conn.request('HEAD', '/')
309 headers = conn.getresponse().getheaders() 319 headers = conn.getresponse().getheaders()
310 server_header = dict(headers)['server'] 320 server_header = dict(headers)['server']
311 self.assertIn('TornadoServer', server_header) 321 self.assertIn('TornadoServer', server_header)
312 322
313 @unittest.skipIf( 323 @unittest.skipIf(
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
472 'RequestId': self.counter.next(), 482 'RequestId': self.counter.next(),
473 'Type': 'Deployer', 483 'Type': 'Deployer',
474 'Request': 'Status', 484 'Request': 'Status',
475 }) 485 })
476 self.assertIn('LastChanges', response['Response']) 486 self.assertIn('LastChanges', response['Response'])
477 changes = response['Response']['LastChanges'] 487 changes = response['Response']['LastChanges']
478 self.assertEqual(2, len(changes)) 488 self.assertEqual(2, len(changes))
479 statuses = [change['Status'] for change in changes] 489 statuses = [change['Status'] for change in changes]
480 self.assertEqual(['completed', 'completed'], statuses) 490 self.assertEqual(['completed', 'completed'], statuses)
481 491
492 def test_nrpe_check_available(self):
493 # Make sure the check-app-access.sh script's ADDRESS is available.
494 conn = httplib.HTTPSConnection(self.hostname)
495 # This request matches the ADDRESS var in the script.
496 conn.request('GET', '/juju-ui/version.js')
497 message = 'ADDRESS in check-app-access.sh is not accessible.'
498 self.assertEqual(200, conn.getresponse().status, message)
499
482 500
483 if __name__ == '__main__': 501 if __name__ == '__main__':
484 unittest.main(verbosity=2) 502 unittest.main(verbosity=2)
OLDNEW
« no previous file with comments | « revision ('k') | tests/deploy.py » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b