127
135
services = self.wait_for(services_found, 'Services not displayed.')
128
136
return set([element.text for element in services])
130
def stop_services(self, hostname, services):
138
def juju_deploy(self, *args, **kwargs):
139
"""Shim in our additional cleanup for pyJuju."""
131
140
# XXX 2012-11-29 frankban bug=872264:
132
141
# Just invoking ``juju destroy-service juju-gui`` in tearDown
133
142
# should execute the ``stop`` hook, stopping all the services
134
143
# started by the charm in the machine. Right now this does not
135
144
# work in pyJuju, so the desired effect is achieved by keeping
136
145
# track of started services and manually stopping them here.
137
target = 'ubuntu@{}'.format(hostname)
138
for service in services:
139
ssh(target, 'sudo', 'service', service, 'stop')
141
def juju_deploy(self, *args, **kws):
142
"""Shim in our additional cleanup for pyJuju."""
143
# XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
144
146
# Once pyJuju works correctly or we drop support for it altogether, we
145
147
# can remove this shim.
146
unit_info = juju_deploy(*args, **kws)
148
unit_info = juju_deploy(*args, **kwargs)
147
149
if is_legacy_juju:
148
150
hostname = unit_info['public-address']
149
services = ['haproxy', 'apache2']
151
options = kwargs.get('options', {})
152
# Either stop the builtin server or the old apache2/haproxy setup.
153
if options.get('builtin-server') == 'true':
154
services = ['guiserver']
156
services = ['haproxy', 'apache2']
150
157
# Staging uses improv, otherwise the API agent is used.
151
if kws.get('options').get('staging') == 'true':
158
if options.get('staging') == 'true':
152
159
services.append('juju-api-improv')
154
161
services.append('juju-api-agent')
155
self.addCleanup(self.stop_services, hostname, services)
162
self.addCleanup(stop_services, hostname, services)
159
class DeployTest(DeployTestMixin, unittest.TestCase):
161
def test_api_agent(self):
162
# Ensure the Juju GUI and API agent services are correctly set up.
166
class TestDeploy(DeployTestMixin, unittest.TestCase):
169
juju_destroy_service(self.charm)
171
def test_local_release(self):
172
# Ensure the Juju GUI and API agent services are correctly set up when
173
# deploying the local release.
163
174
unit_info = self.juju_deploy(self.charm)
164
175
hostname = unit_info['public-address']
165
176
self.navigate_to(hostname)
166
177
self.handle_browser_warning()
167
178
self.assertEnvironmentIsConnected()
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()
169
190
@unittest.skipUnless(is_legacy_juju, 'staging only works in pyJuju')
170
191
def test_staging(self):
171
192
# Ensure the Juju GUI and improv services are correctly set up.
180
201
def test_sandbox(self):
181
202
# The GUI is correctly deployed and set up in sandbox mode.
182
unit_info = self.juju_deploy(
183
self.charm, options={'builtin-server': 'true', 'sandbox': 'true'})
184
hostname = unit_info['public-address']
185
self.navigate_to(hostname)
186
self.handle_browser_warning()
187
self.assertEnvironmentIsConnected()
189
def test_builtin_server(self):
190
# Ensure the Juju GUI and builtin server are correctly set up.
191
unit_info = self.juju_deploy(
192
self.charm, options={'builtin-server': 'true'})
193
hostname = unit_info['public-address']
194
self.navigate_to(hostname)
195
self.handle_browser_warning()
196
self.assertEnvironmentIsConnected()
197
conn = httplib.HTTPSConnection(hostname)
198
conn.request('HEAD', '/')
199
headers = conn.getresponse().getheaders()
200
server_header = dict(headers)['server']
201
self.assertIn('TornadoServer', server_header)
203
unit_info = self.juju_deploy(self.charm, options={'sandbox': 'true'})
204
hostname = unit_info['public-address']
205
self.navigate_to(hostname)
206
self.handle_browser_warning()
207
self.assertEnvironmentIsConnected()
203
209
def test_branch_source(self):
204
210
# Ensure the Juju GUI is correctly deployed from a Bazaar branch.
205
unit_info = self.juju_deploy(
206
self.charm, options={'juju-gui-source': JUJU_GUI_TEST_BRANCH})
211
options = {'juju-gui-source': JUJU_GUI_TEST_BRANCH}
212
unit_info = self.juju_deploy(self.charm, options=options)
207
213
hostname = unit_info['public-address']
208
214
self.navigate_to(hostname)
209
215
self.handle_browser_warning()
210
216
self.assertEnvironmentIsConnected()
212
def test_cache_headers(self):
213
# Make sure the correct cache headers are sent.
214
unit_info = self.juju_deploy(
215
self.charm, options={'juju-gui-source': JUJU_GUI_TEST_BRANCH})
218
def test_legacy_server(self):
219
# The legacy apache + haproxy server configuration works correctly.
220
# Also make sure the correct cache headers are sent.
222
'builtin-server': False,
223
'juju-gui-source': JUJU_GUI_TEST_BRANCH,
225
unit_info = self.juju_deploy(self.charm, options=options)
216
226
hostname = unit_info['public-address']
217
227
conn = httplib.HTTPSConnection(hostname)
218
228
conn.request('HEAD', '/')
247
257
self.assertEqual(200, conn.getresponse().status, message)
260
class TestBuiltinServer(DeployTestMixin, unittest.TestCase):
264
# Deploy the charm. The resulting service is used by all the tests
266
unit_info = juju_deploy(cls.charm)
267
cls.hostname = unit_info['public-address']
268
# The counter is used to produce API request identifiers.
269
cls.counter = itertools.count()
272
def tearDownClass(cls):
273
# Destroy the GUI service, and perform additional clean up in the case
274
# we are in a pyJuju environment.
275
juju_destroy_service(cls.charm)
277
# XXX 2012-11-29 frankban bug=872264:
278
# see DeployTestMixin.juju_deploy above.
279
stop_services(cls.hostname, ['guiserver', 'juju-api-agent'])
281
def make_websocket_client(self, authenticated=True):
282
"""Create and return a WebSocket client connected to the Juju backend.
284
If authenticated is set to True, also log in to the Juju API server.
286
client = WebSocketClient('wss://{}:443/ws'.format(self.hostname))
288
self.addCleanup(client.close)
290
response = client.send({
291
'RequestId': self.counter.next(),
294
'Params': {'AuthTag': 'user-admin', 'Password': admin_secret},
296
self.assertNotIn('Error', response)
299
def test_environment_connection(self):
300
# Ensure the Juju GUI and builtin server are correctly set up.
301
self.navigate_to(self.hostname)
302
self.handle_browser_warning()
303
self.assertEnvironmentIsConnected()
305
def test_headers(self):
306
# Ensure the Tornado headers are correctly sent.
307
conn = httplib.HTTPSConnection(self.hostname)
308
conn.request('HEAD', '/')
309
headers = conn.getresponse().getheaders()
310
server_header = dict(headers)['server']
311
self.assertIn('TornadoServer', server_header)
314
is_legacy_juju, 'bundle deployments are only supported in juju-core')
315
def test_deployer_not_authenticated(self):
316
# An error is returned trying to start a bundle deployment without
317
# being authenticated.
318
client = self.make_websocket_client(authenticated=False)
319
response = client.send({
320
'RequestId': self.counter.next(),
323
'Params': {'Name': 'bundle-name', 'YAML': 'foo: bar'},
325
self.assertIn('Error', response)
327
'unauthorized access: no user logged in', response['Error'])
329
@unittest.skipUnless(admin_secret, 'admin secret was not found')
331
is_legacy_juju, 'bundle deployments are only supported in juju-core')
332
def test_deployer_invalid_bundle_name(self):
333
# An error is returned trying to deploy a bundle with an invalid name.
334
client = self.make_websocket_client()
335
response = client.send({
336
'RequestId': self.counter.next(),
339
'Params': {'Name': 'no-such', 'YAML': example.BUNDLE1},
341
self.assertIn('Error', response)
343
'invalid request: bundle no-such not found', response['Error'])
345
@unittest.skipUnless(admin_secret, 'admin secret was not found')
347
is_legacy_juju, 'bundle deployments are only supported in juju-core')
348
def test_deployer_invalid_bundle_yaml(self):
349
# An error is returned trying to deploy an invalid bundle YAML.
350
client = self.make_websocket_client()
351
response = client.send({
352
'RequestId': self.counter.next(),
355
'Params': {'Name': 'bundle-name', 'YAML': 42},
357
self.assertIn('Error', response)
359
'invalid request: invalid YAML contents', response['Error'])
361
@unittest.skipUnless(admin_secret, 'admin secret was not found')
363
is_legacy_juju, 'bundle deployments are only supported in juju-core')
364
def test_deployer_watch_unknown_deployment(self):
365
# An error is returned trying to watch an unknown deployment.
366
client = self.make_websocket_client()
367
response = client.send({
368
'RequestId': self.counter.next(),
371
'Params': {'DeploymentId': 424242},
373
self.assertIn('Error', response)
375
'invalid request: deployment not found', response['Error'])
377
@unittest.skipUnless(admin_secret, 'admin secret was not found')
379
is_legacy_juju, 'bundle deployments are only supported in juju-core')
380
def test_deployer(self):
381
# The builtin server supports deploying bundles using juju-deployer.
382
client = self.make_websocket_client()
384
# Start a first bundle deployment.
385
response = client.send({
386
'RequestId': self.counter.next(),
389
'Params': {'Name': 'bundle1', 'YAML': example.BUNDLE1},
391
self.assertNotIn('Error', response)
392
self.assertIn('DeploymentId', response['Response'])
393
# Schedule the removal of the services deployed processing the bundle.
394
bundle_data = yaml.safe_load(example.BUNDLE1)
395
services = bundle_data['bundle1']['services'].keys()
396
for service in services:
397
self.addCleanup(juju_destroy_service, service)
399
# Start a second bundle deployment: the bundle name can be omitted if
400
# the YAML contains only one bundle.
401
response = client.send({
402
'RequestId': self.counter.next(),
405
'Params': {'YAML': example.BUNDLE2},
407
self.assertNotIn('Error', response)
408
self.assertIn('DeploymentId', response['Response'])
409
# Store the deployment id to be used later.
410
deployment_id = response['Response']['DeploymentId']
411
# Schedule the removal of the services deployed processing the bundle.
412
bundle_data = yaml.safe_load(example.BUNDLE2)
413
services = bundle_data['bundle2']['services'].keys()
414
for service in services:
415
self.addCleanup(juju_destroy_service, service)
417
# Check the bundle deployments status.
418
response = client.send({
419
'RequestId': self.counter.next(),
423
self.assertIn('LastChanges', response['Response'])
424
changes = response['Response']['LastChanges']
425
self.assertEqual(2, len(changes))
426
change1, change2 = changes
427
self.assertEqual(0, change1['Queue'])
428
self.assertEqual('started', change1['Status'])
429
self.assertEqual(1, change2['Queue'])
430
self.assertEqual('scheduled', change2['Status'])
432
# Start watching the second deployment.
433
response = client.send({
434
'RequestId': self.counter.next(),
437
'Params': {'DeploymentId': deployment_id},
439
self.assertNotIn('Error', response)
440
self.assertIn('WatcherId', response['Response'])
441
watcher_id = response['Response']['WatcherId']
443
# Observe three changes on the second deployment.
444
for status in ('scheduled', 'started', 'completed'):
445
response = client.send({
446
'RequestId': self.counter.next(),
449
'Params': {'WatcherId': watcher_id},
451
self.assertNotIn('Error', response)
452
self.assertIn('Changes', response['Response'])
453
changes = response['Response']['Changes']
454
self.assertEqual(1, len(changes))
455
self.assertEqual(status, changes[0]['Status'])
457
# An error is returned trying to re-deploy a bundle.
458
response = client.send({
459
'RequestId': self.counter.next(),
462
'Params': {'YAML': example.BUNDLE1},
464
self.assertIn('Error', response)
466
'invalid request: service(s) already in the environment: '
470
# Check the final bundle deployment status.
471
response = client.send({
472
'RequestId': self.counter.next(),
476
self.assertIn('LastChanges', response['Response'])
477
changes = response['Response']['LastChanges']
478
self.assertEqual(2, len(changes))
479
statuses = [change['Status'] for change in changes]
480
self.assertEqual(['completed', 'completed'], statuses)
250
483
if __name__ == '__main__':
251
484
unittest.main(verbosity=2)