Merge lp:~juju-gui/charms/precise/juju-gui/trunk into lp:~charmers/charms/precise/juju-gui/trunk

Proposed by Gary Poster
Status: Merged
Approved by: Mark Mims
Approved revision: 34
Merged at revision: 32
Proposed branch: lp:~juju-gui/charms/precise/juju-gui/trunk
Merge into: lp:~charmers/charms/precise/juju-gui/trunk
Diff against target: 1381 lines (+502/-214)
18 files modified
HACKING.md (+2/-2)
README.md (+3/-7)
config.yaml (+14/-5)
config/config.js.template (+10/-6)
config/haproxy.cfg.template (+42/-0)
config/haproxy.conf (+22/-0)
config/juju-api-agent.conf.template (+4/-4)
config/juju-api-improv.conf.template (+4/-4)
config/nginx-site.template (+18/-14)
config/nginx.conf (+1/-1)
hooks/config-changed (+21/-25)
hooks/install (+33/-22)
hooks/start (+11/-20)
hooks/stop (+6/-10)
hooks/utils.py (+132/-31)
revision (+1/-1)
tests/deploy.test (+63/-32)
tests/test_utils.py (+115/-30)
To merge this branch: bzr merge lp:~juju-gui/charms/precise/juju-gui/trunk
Reviewer Review Type Date Requested Status
Mark Mims (community) Approve
Review via email: mp+152429@code.launchpad.net

Commit message

- Add support for Firefox and self-signed certs
- Add support to help us integrate the charm into CI tests
- Address problem with newer charmhelpers incompatibility

Description of the change

- Add support for Firefox and self-signed certs
- Add support to help us integrate the charm into CI tests
- Address problem with newer charmhelpers incompatibility

mgz fixed the last problem separately and differently in https://code.launchpad.net/~gz/charms/precise/juju-gui/install_log_error_1152631/+merge/152421

To post a comment you must log in.
34. By Gary Poster

add support for socket_protocol so that the websocket address can be generated dynamically in the browser for greater deployment flexibility (such as canonistack), while maintaining backwards compatibility with earlier GUI releases. Note that, as of this writing, you need to use lp:juju-gui (trunk) to make this dynamic addressing work.
add ability to deploy insecurely (http/ws), in order to facilitate CI tests
add tests for new functionality
update documentation
speed up default deployment by only getting staging dependencies when needed, and only getting build dependencies when needed (bug 1117896). On EC2, this speeds up the default deployment (from release) by about 2.5 minutes.
work around backward incompatibility in update to charmhelpers' log function.
increase timeout for tests to make them pass more often

Revision history for this message
Mark Mims (mark-mims) wrote :

It appears that these changes get reviewed before they land in lp:~juju-gui/charms/precise/juju-gui/trunk so I'm rubber-stamping this.

This team is a perfect example of a "charm team", and once we verify the process works, we'll make lp:~juju-gui/charms/precise/juju-gui/trunk the "official" lp:charms/juju-gui branch. Then reviews won't block on ~charmers... Anyone in ~juju-gui (or ~charmers) can review/merge/push.

review: Approve
Revision history for this message
Mark Mims (mark-mims) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'HACKING.md'
2--- HACKING.md 2013-01-11 17:56:35 +0000
3+++ HACKING.md 2013-03-13 22:47:20 +0000
4@@ -16,7 +16,7 @@
5
6 You'll also need some dependencies and developer basics.
7
8- sudo apt-get install bzr autoconf libtool python-charmhelpers
9+ sudo apt-get install bzr autoconf libtool python-charmhelpers xvfb
10
11 Next, you need the bzr branch. We work from
12 [lp:~juju-gui/charms/precise/juju-gui/trunk](https://code.launchpad.net/~juju-gui/charms/precise/juju-gui/trunk).
13@@ -29,7 +29,7 @@
14 locally. The files may be installed globally, or into your home directory (as
15 here):
16
17- sudo apt-get install autoconf libtool python-charmhelpers
18+ sudo apt-get install autoconf libtool python-charmhelpers python-tempita
19 bzr branch lp:~jimbaker/juju-jitsu/unit-test jitsu-unit-test
20 cd jitsu-unit-test
21 autoreconf
22
23=== modified file 'README.md'
24--- README.md 2013-01-24 16:37:54 +0000
25+++ README.md 2013-03-13 22:47:20 +0000
26@@ -6,8 +6,7 @@
27
28 The Juju GUI supports recent releases of Chrome and Chromium. Recent Firefox
29 releases are also supported, but regressions may occur until the completion of
30-upcoming continuous integration work. Internet Explorer 10 will be supported
31-soon.
32+upcoming continuous integration work.
33
34 ## Demo/Staging Server ##
35
36@@ -50,7 +49,6 @@
37 open-ports:
38 - 80/tcp
39 - 443/tcp
40- - 8080/tcp
41 public-address: ec2-www-xxx-yyy-zzz.compute-1.amazonaws.com
42
43 That tells me I can go to the public-address in my browser via HTTPS
44@@ -58,10 +56,8 @@
45 start configuring the rest of Juju with the GUI. You should see a similar
46 web address. Accessing the GUI via HTTP will redirect to using HTTPS.
47
48-Note that currently, when using Firefox with self-signed certificates, you need
49-to first manually add a TLS certificate exception for the Juju API port
50-(default is 8080, see `juju-api-port` in `config.yaml`), and only then do the
51-same for the main Juju GUI HTTPS connection on port 443.
52+By default, the deployment uses self-signed certificates. The browser will ask
53+you to accept a security exception once.
54
55 You will see a login form with the username fixed to "admin". The password is
56 the same as your Juju environment's `admin-secret`, found in
57
58=== modified file 'config.yaml'
59--- config.yaml 2013-01-23 16:41:38 +0000
60+++ config.yaml 2013-03-13 22:47:20 +0000
61@@ -17,11 +17,6 @@
62 The Juju API Bazaar branch (implementing the websocket server).
63 type: string
64 default: lp:~hazmat/juju/rapi-rollup
65- juju-api-port:
66- description: |
67- The port used by the websocket server.
68- type: int
69- default: 8080
70 staging:
71 description: |
72 Connect the Juju GUI to the staging backend
73@@ -78,3 +73,17 @@
74 to mutate the environment.
75 type: boolean
76 default: false
77+ serve-tests:
78+ description: |
79+ Whether or not the GUI unit tests are exposed. If this option is enabled,
80+ unit tests can be run in the browser by visiting the URL
81+ "https://[Juju GUI address]/test/".
82+ type: boolean
83+ default: false
84+ secure:
85+ description: |
86+ In order to run the GUI over a non secure connection (HTTP) set this flag
87+ to false. Do not set this property unless you understand and accept the
88+ risks.
89+ type: boolean
90+ default: true
91
92=== modified file 'config/config.js.template'
93--- config/config.js.template 2013-01-16 10:53:11 +0000
94+++ config/config.js.template 2013-03-13 22:47:20 +0000
95@@ -1,14 +1,18 @@
96 var juju_config = {
97- consoleEnabled: %(console_enabled)s,
98+ consoleEnabled: {{console_enabled}},
99 serverRouting: false,
100 html5: true,
101 container: '#main',
102 viewContainer: '#main',
103 transitions: false,
104 charm_store_url: 'https://jujucharms.com/',
105- socket_url: 'wss://%(address)s:%(port)s/ws',
106- user: %(user)s,
107- password: %(password)s,
108- readOnly: %(readonly)s,
109- login_help: %(login_help)s
110+ // socket_url is only honored in older versions of the GUI.
111+ socket_url: '{{raw_protocol}}://{{address}}/ws',
112+ // socket_protocol is used instead by newer versions of the GUI to
113+ // dynamically generate the websocket address.
114+ socket_protocol: {{protocol}},
115+ user: {{user}},
116+ password: {{password}},
117+ readOnly: {{readonly}},
118+ login_help: {{login_help}}
119 };
120
121=== added file 'config/haproxy.cfg.template'
122--- config/haproxy.cfg.template 1970-01-01 00:00:00 +0000
123+++ config/haproxy.cfg.template 2013-03-13 22:47:20 +0000
124@@ -0,0 +1,42 @@
125+global
126+ maxconn 4096
127+ user haproxy
128+ group haproxy
129+ daemon
130+ # Set the base path for the WebSocket TLS certificate.
131+ ca-base {{ssl_cert_path}}
132+ # Set the base path for the HTTPS TLS certificate.
133+ crt-base {{ssl_cert_path}}
134+
135+defaults
136+ log global
137+ maxconn 4096
138+ mode http
139+ option http-server-close
140+ timeout connect 5s
141+ timeout client 30s
142+ timeout server 30s
143+ # Set a long timeout for WebSocket connections.
144+ timeout tunnel 8h
145+
146+backend juju
147+ # The Juju WebSocket backend.
148+ # Re-encrypt outgoing connections.
149+ server ws1 127.0.0.1:{{api_port}} ssl ca-file {{api_pem}} verify required check-ssl inter 500ms
150+
151+backend web
152+ # Web traffic.
153+ server web1 127.0.0.1:{{web_port}} check inter 500ms
154+
155+frontend public
156+ # Redirect all HTTP traffic to HTTPS.
157+ bind :80
158+ {{if secure}}
159+ redirect scheme https if !{ ssl_fc }
160+ {{endif}}
161+ # Handle HTTPS.
162+ bind :443 ssl crt {{web_pem}}
163+ # Send WebSocket connections to the Juju backend.
164+ use_backend juju if { path /ws }
165+ # Send everything else to the web server.
166+ default_backend web
167
168=== added file 'config/haproxy.conf'
169--- config/haproxy.conf 1970-01-01 00:00:00 +0000
170+++ config/haproxy.conf 2013-03-13 22:47:20 +0000
171@@ -0,0 +1,22 @@
172+description "HAProxy"
173+author "Canonical"
174+
175+start on (filesystem and net-device-up IFACE=lo)
176+stop on runlevel [!2345]
177+
178+env CONF=/etc/haproxy/haproxy.cfg
179+env DAEMON=/usr/sbin/haproxy
180+
181+expect fork
182+respawn
183+respawn limit 10 5
184+
185+pre-start script
186+ # Test configuration, exit if errors are found.
187+ $DAEMON -c -f $CONF
188+ if [ $? -ne 0 ]
189+ then exit $?
190+ fi
191+end script
192+
193+exec $DAEMON -f $CONF
194
195=== modified file 'config/juju-api-agent.conf.template'
196--- config/juju-api-agent.conf.template 2013-01-03 15:02:38 +0000
197+++ config/juju-api-agent.conf.template 2013-03-13 22:47:20 +0000
198@@ -4,11 +4,11 @@
199 start on runlevel [2345]
200 stop on runlevel [!2345]
201
202-env PYTHONPATH=%(juju_dir)s:$PYTHONPATH
203-env JUJU_ZOOKEEPER=%(zookeeper)s
204+env PYTHONPATH={{juju_dir}}:$PYTHONPATH
205+env JUJU_ZOOKEEPER={{zookeeper}}
206
207 # Use --nodaemon so that upstart can correctly retrieve the process ID.
208-exec /usr/bin/python -m juju.agents.api --nodaemon --port %(port)s \
209+exec /usr/bin/python -m juju.agents.api --nodaemon --port {{port}} \
210 --logfile /var/log/juju/api-agent.log \
211 --session-file /var/run/juju/api-agent.zksession \
212- --secure --keys %(keys)s
213+ --secure --keys {{keys}}
214
215=== modified file 'config/juju-api-improv.conf.template'
216--- config/juju-api-improv.conf.template 2013-01-03 15:02:38 +0000
217+++ config/juju-api-improv.conf.template 2013-03-13 22:47:20 +0000
218@@ -5,8 +5,8 @@
219 stop on runlevel [!2345]
220
221 setuid ubuntu
222-env PYTHONPATH=%(juju_dir)s:$PYTHONPATH
223+env PYTHONPATH={{juju_dir}}:$PYTHONPATH
224
225-exec /usr/bin/python %(juju_dir)s/improv.py --port %(port)s \
226- -f %(juju_dir)s/%(staging_env)s.json \
227- --secure --keys %(keys)s
228+exec /usr/bin/python {{juju_dir}}/improv.py --port {{port}} \
229+ -f {{juju_dir}}/{{staging_env}}.json \
230+ --secure --keys {{keys}}
231
232=== renamed file 'config/nginx.conf.template' => 'config/nginx-site.template'
233--- config/nginx.conf.template 2013-01-03 14:17:21 +0000
234+++ config/nginx-site.template 2013-03-13 22:47:20 +0000
235@@ -1,29 +1,33 @@
236 server {
237- listen 80;
238- server_name _;
239- return 301 https://$host$request_uri;
240-}
241-
242-server {
243- listen 443 default_server ssl;
244- server_name _;
245- root %(server_root)s;
246+ listen 127.0.0.1:{{port}} default_server;
247+ server_name _;
248+ root {{server_root}};
249 index index.html;
250- ssl_certificate %(ssl_cert_path)s/juju.crt;
251- ssl_certificate_key %(ssl_cert_path)s/juju.key;
252
253 # Serve static assets.
254 location ^~ /juju-ui/ {
255- root %(server_root)s;
256+ root {{server_root}};
257 }
258 location = /favicon.ico {
259 expires 10d;
260- root %(server_root)s;
261+ root {{server_root}};
262 }
263 location = /index.html {
264- root %(server_root)s;
265+ root {{server_root}};
266 }
267
268+ {{if serve_tests}}
269+ # Serve unit tests.
270+ location ^~ /test/ {
271+ alias {{tests_root}};
272+ }
273+ {{else}}
274+ # Redirect to site root so that the test URL does not directly
275+ # reference the manifest file. This avoids automatically adding the URL
276+ # to the application cache.
277+ rewrite ^/test/ $scheme://$host/ redirect;
278+ {{endif}}
279+
280 # Fall through to the single-page app for all other URLs.
281 location ~ / {
282 rewrite .* /index.html last;
283
284=== renamed file 'config/juju-gui.conf.template' => 'config/nginx.conf'
285--- config/juju-gui.conf.template 2012-12-12 13:23:56 +0000
286+++ config/nginx.conf 2013-03-13 22:47:20 +0000
287@@ -1,4 +1,4 @@
288-description "Juju GUI"
289+description "nginx"
290 author "Canonical"
291 # Original author: George Shammas <georgyo@gmail.com>.
292 # See http://wiki.nginx.org/Upstart.
293
294=== modified file 'hooks/config-changed'
295--- hooks/config-changed 2013-01-23 16:41:38 +0000
296+++ hooks/config-changed 2013-03-13 22:47:20 +0000
297@@ -4,14 +4,11 @@
298 # Copyright 2012 Canonical Ltd. This software is licensed under the
299 # GNU Affero General Public License version 3 (see the file LICENSE).
300
301-from subprocess import CalledProcessError
302 import sys
303
304 from charmhelpers import (
305 get_config,
306 log,
307- log_entry,
308- log_exit,
309 service_control,
310 STOP,
311 )
312@@ -25,8 +22,11 @@
313 config_json,
314 fetch_api,
315 fetch_gui,
316- GUI,
317+ get_staging_dependencies,
318+ HAPROXY,
319 IMPROV,
320+ log_hook,
321+ NGINX,
322 save_or_create_certificates,
323 setup_gui,
324 start_agent,
325@@ -40,7 +40,6 @@
326 log('Updating configuration.')
327
328 added_or_changed = diff.added_or_changed
329- juju_api_port = config.get('juju-api-port')
330 in_staging = config.get('staging')
331
332 # The juju_gui_source_changed and juju_api_branch_changed variables
333@@ -70,14 +69,15 @@
334
335 # Handle changes to the improv server configuration.
336 if in_staging:
337- staging_properties = set(
338- ['staging', 'staging-environment', 'juju-api-port'])
339+ staging_properties = set(['staging', 'staging-environment'])
340 staging_changed = added_or_changed & staging_properties
341 if staging_changed or ssl_changed or juju_api_branch_changed:
342 if 'staging' in added_or_changed:
343 # 'staging' went from False to True, so the agent server is
344 # running and must be stopped.
345 current_api = AGENT
346+ # We need to make sure we have staging dependencies.
347+ get_staging_dependencies()
348 else:
349 # Only staging parameters changed, so the existing staging
350 # server must be stopped and later restarted.
351@@ -86,13 +86,12 @@
352 service_control(current_api, STOP)
353 # Now the improv server can be cleanly started.
354 log('Starting or restarting staging.')
355- start_improv(juju_api_port, config.get('staging-environment'),
356+ start_improv(config.get('staging-environment'),
357 config['ssl-cert-path'])
358 else:
359- agent_properties = set(['juju-api-port', 'staging'])
360- agent_changed = added_or_changed & agent_properties
361+ agent_changed = 'staging' in added_or_changed
362 if agent_changed or ssl_changed or juju_api_branch_changed:
363- if 'staging' in added_or_changed:
364+ if agent_changed:
365 # If 'staging' transitions to False we need to stop the backend
366 # and start the agent.
367 current_api = IMPROV
368@@ -102,22 +101,26 @@
369 current_api = AGENT
370 service_control(current_api, STOP)
371 log('Starting or restarting Juju API agent.')
372- start_agent(juju_api_port, config['ssl-cert-path'])
373+ start_agent(config['ssl-cert-path'])
374
375 # Handle changes to the juju-gui configuration.
376 gui_properties = set([
377- 'juju-api-port', 'juju-gui-console-enabled', 'login-help', 'read-only',
378- 'staging'])
379+ 'juju-gui-console-enabled', 'login-help', 'read-only', 'serve-tests',
380+ 'staging', 'secure'])
381 gui_changed = added_or_changed & gui_properties
382 if gui_changed or ssl_changed or juju_gui_source_changed:
383 with su('root'):
384- service_control(GUI, STOP)
385+ service_control(HAPROXY, STOP)
386+ service_control(NGINX, STOP)
387 console_enabled = config.get('juju-gui-console-enabled')
388 login_help = config['login-help']
389 readonly = config['read-only']
390+ serve_tests = config['serve-tests']
391 ssl_cert_path = config['ssl-cert-path']
392- start_gui(juju_api_port, console_enabled, login_help, readonly,
393- in_staging, ssl_cert_path)
394+ secure = config['secure']
395+ start_gui(
396+ console_enabled, login_help, readonly, in_staging, ssl_cert_path,
397+ serve_tests, secure=secure)
398
399
400 def main():
401@@ -133,12 +136,5 @@
402
403
404 if __name__ == '__main__':
405- log_entry()
406- try:
407+ with log_hook():
408 main()
409- except CalledProcessError as e:
410- log('Exception caught:')
411- log(e.output)
412- raise
413- finally:
414- log_exit()
415
416=== modified file 'hooks/install'
417--- hooks/install 2013-01-23 16:11:16 +0000
418+++ hooks/install 2013-03-13 22:47:20 +0000
419@@ -1,10 +1,10 @@
420 #!/usr/bin/env python2
421 # -*- python -*-
422
423-from subprocess import (
424- CalledProcessError,
425- check_call,
426-)
427+import os
428+import shutil
429+from subprocess import check_call
430+
431
432 # If the user's environment has "juju-origin: ppa" set, they will
433 # automatically have access to python-charmhelpers and python-shelltoolbox.
434@@ -14,17 +14,20 @@
435 import bootstrap_utils
436 bootstrap_utils.install_extra_repositories('ppa:juju/pkgs')
437
438-# python-shelltoolbox is installed as a dependency of python-charmhelpers.
439-check_call(['apt-get', 'install', '-y', 'python-charmhelpers'])
440-
441-
442-# These modules depend on charmhelpers and shelltoolbox being installed so they
443+# Python dependencies must be installed here so that the charm can import and
444+# use required libraries.
445+PYTHON_DEPENDENCIES = (
446+ 'python-charmhelpers', 'python-launchpadlib', 'python-shelltoolbox',
447+ 'python-tempita',
448+)
449+check_call(('apt-get', 'install', '-y') + PYTHON_DEPENDENCIES)
450+
451+
452+# These modules depend on the Python dependencies above being installed so they
453 # must not be imported until those packages are available.
454 from charmhelpers import (
455 get_config,
456 log,
457- log_entry,
458- log_exit,
459 )
460 from shelltoolbox import (
461 apt_get_install,
462@@ -36,27 +39,41 @@
463 config_json,
464 fetch_api,
465 fetch_gui,
466+ log_hook,
467+ get_staging_dependencies,
468 save_or_create_certificates,
469 setup_gui,
470 setup_nginx,
471 )
472
473
474+CONFIG_FILES = ('haproxy.conf', 'nginx.conf')
475+REPOSITORIES = ('ppa:juju-gui/ppa',)
476 DEB_DEPENDENCIES = (
477- 'bzr', 'curl', 'imagemagick', 'make', 'nginx', 'nodejs', 'npm', 'openssl',
478- 'python-launchpadlib', 'zookeeper',
479+ 'curl', 'haproxy', 'nginx', 'openssl',
480 )
481
482
483 def get_dependencies():
484+ """Install deb dependencies."""
485 log('Installing dependencies.')
486- cmd_log(install_extra_repositories('ppa:chris-lea/node.js'))
487+ cmd_log(install_extra_repositories(*REPOSITORIES))
488 cmd_log(apt_get_install(*DEB_DEPENDENCIES))
489
490
491+def setup_services():
492+ """Set up haproxy and nginx upstart configuration files."""
493+ log('Setting up haproxy and nginx start up scripts.')
494+ source_dir = os.path.join(os.path.dirname(__file__), '..', 'config')
495+ for config_file in CONFIG_FILES:
496+ shutil.copy(os.path.join(source_dir, config_file), '/etc/init/')
497+
498+
499 def main():
500 config = get_config()
501 get_dependencies()
502+ if config.get('staging'):
503+ get_staging_dependencies()
504 release_tarball = fetch_gui(
505 config['juju-gui-source'], config['command-log-file'])
506 setup_gui(release_tarball)
507@@ -65,16 +82,10 @@
508 config['ssl-cert-path'], config.get('ssl-cert-contents'),
509 config.get('ssl-key-contents'))
510 fetch_api(config['juju-api-branch'])
511+ setup_services()
512 config_json.set(config)
513
514
515 if __name__ == '__main__':
516- log_entry()
517- try:
518+ with log_hook():
519 main()
520- except CalledProcessError as e:
521- log('Exception caught:')
522- log(e.output)
523- raise
524- finally:
525- log_exit()
526
527=== modified file 'hooks/start'
528--- hooks/start 2013-01-23 16:41:38 +0000
529+++ hooks/start 2013-03-13 22:47:20 +0000
530@@ -4,47 +4,38 @@
531 from charmhelpers import (
532 get_config,
533 log,
534- log_entry,
535- log_exit,
536 open_port,
537 )
538
539 from utils import (
540+ log_hook,
541 start_agent,
542 start_gui,
543 start_improv,
544 )
545
546
547-def open_ports(juju_api_port):
548- """Expose Juju GUI web server and websocket server ports."""
549+def open_ports():
550+ """Expose Juju GUI web server (HTTP and HTTPS) ports."""
551 log('Exposing services.')
552- # Open the Juju GUI web server HTTP and HTTPS ports.
553 open_port(80)
554 open_port(443)
555- # Open the Juju websocket server port.
556- open_port(juju_api_port)
557
558
559 def main():
560 config = get_config()
561- juju_api_port = config['juju-api-port']
562 staging = config.get('staging')
563- start_gui(
564- juju_api_port, config['juju-gui-console-enabled'],
565- config['login-help'], config['read-only'], staging,
566- config['ssl-cert-path'])
567 if staging:
568- start_improv(juju_api_port, config['staging-environment'],
569- config['ssl-cert-path'])
570+ start_improv(config['staging-environment'], config['ssl-cert-path'])
571 else:
572- start_agent(juju_api_port, config['ssl-cert-path'])
573- open_ports(juju_api_port)
574+ start_agent(config['ssl-cert-path'])
575+ start_gui(
576+ config['juju-gui-console-enabled'], config['login-help'],
577+ config['read-only'], staging, config['ssl-cert-path'],
578+ config['serve-tests'], secure=config['secure'])
579+ open_ports()
580
581
582 if __name__ == '__main__':
583- log_entry()
584- try:
585+ with log_hook():
586 main()
587- finally:
588- log_exit()
589
590=== modified file 'hooks/stop'
591--- hooks/stop 2012-12-19 09:57:45 +0000
592+++ hooks/stop 2013-03-13 22:47:20 +0000
593@@ -1,14 +1,13 @@
594 #!/usr/bin/env python2
595 #-*- python -*-
596
597-from charmhelpers import (
598- get_config,
599- log_entry,
600- log_exit,
601+from charmhelpers import get_config
602+
603+from utils import (
604+ log_hook,
605+ stop,
606 )
607
608-from utils import stop
609-
610
611 def main():
612 config = get_config()
613@@ -16,8 +15,5 @@
614
615
616 if __name__ == '__main__':
617- log_entry()
618- try:
619+ with log_hook():
620 main()
621- finally:
622- log_exit()
623
624=== modified file 'hooks/utils.py'
625--- hooks/utils.py 2013-01-23 16:41:38 +0000
626+++ hooks/utils.py 2013-03-13 22:47:20 +0000
627@@ -2,6 +2,7 @@
628
629 __all__ = [
630 'AGENT',
631+ 'API_PORT',
632 'bzr_checkout',
633 'cmd_log',
634 'CURRENT_DIR',
635@@ -9,11 +10,16 @@
636 'fetch_gui',
637 'first_path_in_dir',
638 'get_release_file_url',
639+ 'get_staging_dependencies',
640 'get_zookeeper_address',
641- 'GUI',
642+ 'HAPROXY',
643 'IMPROV',
644 'JUJU_DIR',
645 'JUJU_GUI_DIR',
646+ 'JUJU_GUI_SITE',
647+ 'JUJU_PEM',
648+ 'log_hook',
649+ 'NGINX',
650 'parse_source',
651 'render_to_file',
652 'save_or_create_certificates',
653@@ -23,18 +29,26 @@
654 'start_gui',
655 'start_improv',
656 'stop',
657+ 'WEB_PORT',
658 ]
659
660+from contextlib import contextmanager
661 import json
662 import os
663 import logging
664+import shutil
665+from subprocess import CalledProcessError
666 import tempfile
667+import tempita
668
669 from launchpadlib.launchpad import Launchpad
670 from shelltoolbox import (
671+ apt_get_install,
672 command,
673 environ,
674+ install_extra_repositories,
675 run,
676+ script_name,
677 search_file,
678 Serializer,
679 su,
680@@ -51,11 +65,25 @@
681
682 AGENT = 'juju-api-agent'
683 IMPROV = 'juju-api-improv'
684-GUI = 'juju-gui'
685+HAPROXY = 'haproxy'
686+NGINX = 'nginx'
687+
688+API_PORT = 8080
689+WEB_PORT = 8000
690+
691 CURRENT_DIR = os.getcwd()
692 JUJU_DIR = os.path.join(CURRENT_DIR, 'juju')
693 JUJU_GUI_DIR = os.path.join(CURRENT_DIR, 'juju-gui')
694 JUJU_GUI_SITE = '/etc/nginx/sites-available/juju-gui'
695+JUJU_PEM = 'juju.includes-private-key.pem'
696+BUILD_REPOSITORIES = ('ppa:chris-lea/node.js',)
697+DEB_BUILD_DEPENDENCIES = (
698+ 'bzr', 'imagemagick', 'make', 'nodejs', 'npm',
699+)
700+DEB_STAGE_DEPENDENCIES = (
701+ 'zookeeper',
702+)
703+
704
705 # Store the configuration from on invocation to the next.
706 config_json = Serializer('/tmp/config.json')
707@@ -63,6 +91,19 @@
708 bzr_checkout = command('bzr', 'co', '--lightweight')
709
710
711+def _get_build_dependencies():
712+ """Install deb dependencies for building."""
713+ log('Installing build dependencies.')
714+ cmd_log(install_extra_repositories(*BUILD_REPOSITORIES))
715+ cmd_log(apt_get_install(*DEB_BUILD_DEPENDENCIES))
716+
717+
718+def get_staging_dependencies():
719+ """Install deb dependencies for the stage (improv) environment."""
720+ log('Installing stage dependencies.')
721+ cmd_log(apt_get_install(*DEB_STAGE_DEPENDENCIES))
722+
723+
724 def first_path_in_dir(directory):
725 """Return the full path of the first file/dir in *directory*."""
726 return os.path.join(directory, os.listdir(directory)[0])
727@@ -118,6 +159,25 @@
728 return line.split('=')[1].strip('"')
729
730
731+@contextmanager
732+def log_hook():
733+ """Log when an hook starts and stops its execution.
734+
735+ Also log to stdout possible CalledProcessError exceptions raised executing
736+ the hook.
737+ """
738+ script = script_name()
739+ log(">>> Entering {}".format(script))
740+ try:
741+ yield
742+ except CalledProcessError as err:
743+ log('Exception caught:')
744+ log(err.output)
745+ raise
746+ finally:
747+ log("<<< Exiting {}".format(script))
748+
749+
750 def parse_source(source):
751 """Parse the ``juju-gui-source`` option.
752
753@@ -138,19 +198,21 @@
754 return 'stable', source
755
756
757-def render_to_file(template, context, destination):
758- """Render the given *template* into *destination* using *context*.
759+def render_to_file(template_name, context, destination):
760+ """Render the given *template_name* into *destination* using *context*.
761
762- The arguments *template* is the name or path of the template file: it may
763- be either a path relative to ``../config`` or an absolute path.
764+ The tempita template language is used to render contents
765+ (see http://pythonpaste.org/tempita/).
766+ The argument *template_name* is the name or path of the template file:
767+ it may be either a path relative to ``../config`` or an absolute path.
768 The argument *destination* is a file path.
769 The argument *context* is a dict-like object.
770 """
771 template_path = os.path.join(
772- os.path.dirname(__file__), '..', 'config', template)
773- contents = open(template_path).read()
774+ os.path.dirname(__file__), '..', 'config', template_name)
775+ template = tempita.Template.from_filename(template_path)
776 with open(destination, 'w') as stream:
777- stream.write(contents % context)
778+ stream.write(template.substitute(context))
779
780
781 results_log = None
782@@ -165,7 +227,7 @@
783 filename=config['command-log-file'],
784 level=logging.INFO,
785 format="%(asctime)s: %(name)s@%(levelname)s %(message)s")
786- results_log = logging.getLogger(GUI)
787+ results_log = logging.getLogger('juju-gui')
788
789
790 def cmd_log(results):
791@@ -179,15 +241,15 @@
792 results_log.info('\n' + results)
793
794
795-def start_improv(juju_api_port, staging_env, ssl_cert_path,
796+def start_improv(staging_env, ssl_cert_path,
797 config_path='/etc/init/juju-api-improv.conf'):
798 """Start a simulated juju environment using ``improv.py``."""
799 log('Setting up staging start up script.')
800 context = {
801 'juju_dir': JUJU_DIR,
802- 'port': juju_api_port,
803+ 'keys': ssl_cert_path,
804+ 'port': API_PORT,
805 'staging_env': staging_env,
806- 'keys': ssl_cert_path,
807 }
808 render_to_file('juju-api-improv.conf.template', context, config_path)
809 log('Starting the staging backend.')
810@@ -195,8 +257,7 @@
811 service_control(IMPROV, START)
812
813
814-def start_agent(juju_api_port, ssl_cert_path,
815- config_path='/etc/init/juju-api-agent.conf'):
816+def start_agent(ssl_cert_path, config_path='/etc/init/juju-api-agent.conf'):
817 """Start the Juju agent and connect to the current environment."""
818 # Retrieve the Zookeeper address from the start up script.
819 unit_dir = os.path.realpath(os.path.join(CURRENT_DIR, '..'))
820@@ -205,9 +266,9 @@
821 log('Setting up API agent start up script.')
822 context = {
823 'juju_dir': JUJU_DIR,
824- 'port': juju_api_port,
825+ 'keys': ssl_cert_path,
826+ 'port': API_PORT,
827 'zookeeper': zookeeper,
828- 'keys': ssl_cert_path,
829 }
830 render_to_file('juju-api-agent.conf.template', context, config_path)
831 log('Starting API agent.')
832@@ -216,26 +277,37 @@
833
834
835 def start_gui(
836- juju_api_port, console_enabled, login_help, readonly, in_staging,
837- ssl_cert_path, config_path='/etc/init/juju-gui.conf',
838- nginx_path=JUJU_GUI_SITE, config_js_path=None):
839+ console_enabled, login_help, readonly, in_staging, ssl_cert_path,
840+ serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg',
841+ nginx_path=JUJU_GUI_SITE, config_js_path=None, secure=True):
842 """Set up and start the Juju GUI server."""
843 with su('root'):
844 run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR)
845- build_dir = JUJU_GUI_DIR + '/build-'
846- build_dir += 'debug' if in_staging else 'prod'
847- log('Setting up Juju GUI start up script.')
848- render_to_file('juju-gui.conf.template', {}, config_path)
849+ # XXX 2013-02-05 frankban bug=1116320:
850+ # External insecure resources are still loaded when testing in the
851+ # debug environment. For now, switch to the production environment if
852+ # the charm is configured to serve tests.
853+ if in_staging and not serve_tests:
854+ build_dirname = 'build-debug'
855+ else:
856+ build_dirname = 'build-prod'
857+ build_dir = os.path.join(JUJU_GUI_DIR, build_dirname)
858 log('Generating the Juju GUI configuration file.')
859 user, password = ('admin', 'admin') if in_staging else (None, None)
860+ if secure:
861+ protocol = 'wss'
862+ else:
863+ log('Running in insecure mode! Port 80 will serve unencrypted.')
864+ protocol = 'ws'
865 context = {
866+ 'raw_protocol': protocol,
867 'address': unit_get('public-address'),
868 'console_enabled': json.dumps(console_enabled),
869 'login_help': json.dumps(login_help),
870 'password': json.dumps(password),
871- 'port': juju_api_port,
872 'readonly': json.dumps(readonly),
873 'user': json.dumps(user),
874+ 'protocol': json.dumps(protocol)
875 }
876 if config_js_path is None:
877 config_js_path = os.path.join(
878@@ -243,21 +315,37 @@
879 render_to_file('config.js.template', context, config_js_path)
880 log('Generating the nginx site configuration file.')
881 context = {
882+ 'port': WEB_PORT,
883+ 'serve_tests': serve_tests,
884 'server_root': build_dir,
885- 'ssl_cert_path': ssl_cert_path.rstrip('/'),
886- }
887- render_to_file('nginx.conf.template', context, nginx_path)
888+ 'tests_root': os.path.join(JUJU_GUI_DIR, 'test', ''),
889+ }
890+ render_to_file('nginx-site.template', context, nginx_path)
891+ log('Generating haproxy configuration file.')
892+ context = {
893+ 'api_pem': JUJU_PEM,
894+ 'api_port': API_PORT,
895+ 'ssl_cert_path': ssl_cert_path,
896+ # Use the same certificate for both HTTPS and Websocket connections.
897+ # In the long term, we want separate certs to be used here.
898+ 'web_pem': JUJU_PEM,
899+ 'web_port': WEB_PORT,
900+ 'secure': secure
901+ }
902+ render_to_file('haproxy.cfg.template', context, haproxy_path)
903 log('Starting Juju GUI.')
904 with su('root'):
905 # Start the Juju GUI.
906- service_control(GUI, START)
907+ service_control(NGINX, START)
908+ service_control(HAPROXY, START)
909
910
911 def stop(in_staging):
912 """Stop the Juju API agent."""
913 with su('root'):
914 log('Stopping Juju GUI.')
915- service_control(GUI, STOP)
916+ service_control(HAPROXY, STOP)
917+ service_control(NGINX, STOP)
918 if in_staging:
919 log('Stopping the staging backend.')
920 service_control(IMPROV, STOP)
921@@ -271,6 +359,9 @@
922 # Retrieve a Juju GUI release.
923 origin, version_or_branch = parse_source(juju_gui_source)
924 if origin == 'branch':
925+ # Make sure we have the dependencies necessary for us to actually make
926+ # a build.
927+ _get_build_dependencies()
928 # Create a release starting from a branch.
929 juju_gui_source_dir = os.path.join(CURRENT_DIR, 'juju-gui-source')
930 log('Retrieving Juju GUI source checkout from %s.' % version_or_branch)
931@@ -279,7 +370,7 @@
932 log('Preparing a Juju GUI release.')
933 logdir = os.path.dirname(logpath)
934 fd, name = tempfile.mkstemp(prefix='make-distfile-', dir=logdir)
935- log('Output from "make distfile" sent to', name)
936+ log('Output from "make distfile" sent to %s' % name)
937 with environ(NO_BZR='1'):
938 run('make', '-C', juju_gui_source_dir, 'distfile',
939 stdout=fd, stderr=fd)
940@@ -338,6 +429,9 @@
941
942 If both *ssl_cert_contents* and *ssl_key_contents* are provided, use them
943 as certificates; otherwise, generate them.
944+
945+ Also create a pem file, suitable for use in the haproxy configuration,
946+ concatenating the key and the certificate files.
947 """
948 crt_path = os.path.join(ssl_cert_path, 'juju.crt')
949 key_path = os.path.join(ssl_cert_path, 'juju.key')
950@@ -358,3 +452,10 @@
951 # These are arbitrary test values for the certificate.
952 '/C=GB/ST=Juju/L=GUI/O=Ubuntu/CN=juju.ubuntu.com',
953 '-keyout', key_path, '-out', crt_path))
954+ # Generate the pem file.
955+ pem_path = os.path.join(ssl_cert_path, JUJU_PEM)
956+ if os.path.exists(pem_path):
957+ os.remove(pem_path)
958+ with open(pem_path, 'w') as pem_file:
959+ shutil.copyfileobj(open(key_path), pem_file)
960+ shutil.copyfileobj(open(crt_path), pem_file)
961
962=== modified file 'revision'
963--- revision 2013-01-23 16:41:57 +0000
964+++ revision 2013-03-13 22:47:20 +0000
965@@ -1,1 +1,1 @@
966-25
967+29
968
969=== modified file 'tests/deploy.test'
970--- tests/deploy.test 2013-01-22 11:40:17 +0000
971+++ tests/deploy.test 2013-03-13 22:47:20 +0000
972@@ -6,7 +6,6 @@
973 import urlparse
974
975 from charmhelpers import make_charm_config_file
976-from selenium.common import exceptions
977 from selenium.webdriver import Firefox
978 from selenium.webdriver.support import ui
979 from shelltoolbox import command
980@@ -33,16 +32,15 @@
981 # Create a Selenium browser instance.
982 selenium = self.selenium = Firefox()
983 self.addCleanup(selenium.quit)
984- self.wait = ui.WebDriverWait(selenium, 20)
985
986 def tearDown(self):
987 juju('destroy-service', self.charm)
988
989 def assertEnvironmentIsConnected(self):
990 """Assert the GUI environment is connected to the Juju API agent."""
991- def connected(driver):
992- return driver.execute_script('return app.env.get("connected");')
993- self.wait.until(connected, 'Environment not connected.')
994+ self.wait_for_script(
995+ 'return app.env.get("connected");',
996+ error='Environment not connected.')
997
998 def make_config_file(self, options=None):
999 """Create a charm config file adding, if required, the Juju GUI source.
1000@@ -59,6 +57,19 @@
1001 options.setdefault('juju-gui-source', JUJU_GUI_SOURCE)
1002 return make_charm_config_file({self.charm: options})
1003
1004+ def handle_browser_warning(self):
1005+ """Overstep the browser warning dialog if required."""
1006+ self.wait_for_script(
1007+ 'return window.isBrowserSupported',
1008+ error='Function isBrowserSupported not found.')
1009+ script = 'return window.isBrowserSupported(navigator.userAgent)'
1010+ supported = self.selenium.execute_script(script)
1011+ if not supported:
1012+ continue_button = self.wait_for_css_selector(
1013+ '#browser-warning input',
1014+ error='Browser warning dialog not found.')
1015+ continue_button.click()
1016+
1017 def navigate_to(self, hostname, path='/'):
1018 """Load a page using the current Selenium driver.
1019
1020@@ -72,16 +83,41 @@
1021 def page_ready(driver):
1022 driver.get(url)
1023 return driver.title == 'Juju Admin'
1024- self.wait.until(page_ready, 'Juju GUI not found.')
1025+ self.wait_for(page_ready, error='Juju GUI not found.')
1026+
1027+ def wait_for(self, condition, error=None, timeout=20):
1028+ """Wait for condition to be True.
1029+
1030+ The argument condition is a callable accepting a driver object.
1031+ Fail printing the provided error if timeout is exceeded.
1032+ Otherwise, return the value returned by the condition call.
1033+ """
1034+ wait = ui.WebDriverWait(self.selenium, timeout)
1035+ return wait.until(condition, error)
1036+
1037+ def wait_for_css_selector(self, selector, error=None, timeout=20):
1038+ """Wait until the provided CSS selector is found.
1039+
1040+ Fail printing the provided error if timeout is exceeded.
1041+ Otherwise, return the value returned by the script.
1042+ """
1043+ condition = lambda driver: driver.find_elements_by_css_selector(
1044+ selector)
1045+ elements = self.wait_for(condition, error=error, timeout=timeout)
1046+ return elements[0]
1047+
1048+ def wait_for_script(self, script, error=None, timeout=20):
1049+ """Wait for the given JavaScript snippet to return a True value.
1050+
1051+ Fail printing the provided error if timeout is exceeded.
1052+ Otherwise, return the value returned by the script.
1053+ """
1054+ condition = lambda driver: driver.execute_script(script)
1055+ return self.wait_for(condition, error=error, timeout=timeout)
1056
1057 def login(self, password):
1058 """Log in to access the Juju GUI using the provided *password*."""
1059- def form_displayed(driver):
1060- try:
1061- return driver.find_element_by_css_selector('form')
1062- except exceptions.NoSuchElementException:
1063- return False
1064- form = self.wait.until(form_displayed, 'Login form not found.')
1065+ form = self.wait_for_css_selector('form', 'Login form not found.')
1066 passwd = form.find_element_by_css_selector('input[type=password]')
1067 passwd.send_keys(password)
1068 submit = form.find_element_by_css_selector('input[type=submit]')
1069@@ -91,7 +127,7 @@
1070 """Return the set of services' names displayed in the current page."""
1071 def services_found(driver):
1072 return driver.find_elements_by_css_selector('.service .name')
1073- services = self.wait.until(services_found, 'Services not displayed.')
1074+ services = self.wait_for(services_found, 'Services not displayed.')
1075 return set([element.text for element in services])
1076
1077 def stop_services(self, hostname, services):
1078@@ -129,30 +165,21 @@
1079 hostname = self.deploy()
1080 # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1081 self.addCleanup(
1082- self.stop_services, hostname, ['juju-api-agent', 'juju-gui'])
1083- self.navigate_to(hostname)
1084- self.assertEnvironmentIsConnected()
1085-
1086- def test_customized_api_port(self):
1087- # It is possible to customize the port used by the websocket server.
1088- api_port = 8081
1089- hostname = self.deploy({'juju-api-port': api_port})
1090- # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1091- self.addCleanup(
1092- self.stop_services, hostname, ['juju-api-agent', 'juju-gui'])
1093- self.navigate_to(hostname)
1094- self.assertEnvironmentIsConnected()
1095- ws_url = self.selenium.execute_script(
1096- 'return app.env.get("socket_url");')
1097- self.assertIn(str(api_port), ws_url)
1098+ self.stop_services,
1099+ hostname, ['haproxy', 'nginx', 'juju-api-agent'])
1100+ self.navigate_to(hostname)
1101+ self.handle_browser_warning()
1102+ self.assertEnvironmentIsConnected()
1103
1104 def test_staging(self):
1105 # Ensure the Juju GUI and improv services are correctly set up.
1106 hostname = self.deploy({'staging': 'true'})
1107 # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1108 self.addCleanup(
1109- self.stop_services, hostname, ['juju-api-improv', 'juju-gui'])
1110+ self.stop_services,
1111+ hostname, ['haproxy', 'nginx', 'juju-api-improv'])
1112 self.navigate_to(hostname)
1113+ self.handle_browser_warning()
1114 self.assertEnvironmentIsConnected()
1115 # The staging environment contains five deployed services.
1116 self.assertSetEqual(set(STAGING_SERVICES), self.get_service_names())
1117@@ -162,8 +189,10 @@
1118 hostname = self.deploy({'juju-gui-source': JUJU_GUI_TEST_BRANCH})
1119 # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1120 self.addCleanup(
1121- self.stop_services, hostname, ['juju-api-agent', 'juju-gui'])
1122+ self.stop_services,
1123+ hostname, ['haproxy', 'nginx', 'juju-api-agent'])
1124 self.navigate_to(hostname)
1125+ self.handle_browser_warning()
1126 self.assertEnvironmentIsConnected()
1127
1128
1129@@ -192,8 +221,10 @@
1130 hostname = self.deploy_to()
1131 # XXX 2012-11-29 frankban bug=872264: see *stop_services* above.
1132 self.addCleanup(
1133- self.stop_services, hostname, ['juju-api-agent', 'juju-gui'])
1134+ self.stop_services,
1135+ hostname, ['haproxy', 'nginx', 'juju-api-agent'])
1136 self.navigate_to(hostname)
1137+ self.handle_browser_warning()
1138 self.assertEnvironmentIsConnected()
1139
1140
1141
1142=== modified file 'tests/test_utils.py'
1143--- tests/test_utils.py 2013-01-23 16:41:38 +0000
1144+++ tests/test_utils.py 2013-03-13 22:47:20 +0000
1145@@ -4,16 +4,23 @@
1146 import os
1147 import shutil
1148 from simplejson import dumps
1149+from subprocess import CalledProcessError
1150 import tempfile
1151+import tempita
1152 import unittest
1153
1154 import charmhelpers
1155+
1156 from utils import (
1157 _get_by_attr,
1158+ API_PORT,
1159 cmd_log,
1160 first_path_in_dir,
1161 get_release_file_url,
1162 get_zookeeper_address,
1163+ JUJU_GUI_DIR,
1164+ JUJU_PEM,
1165+ log_hook,
1166 parse_source,
1167 render_to_file,
1168 save_or_create_certificates,
1169@@ -21,6 +28,7 @@
1170 start_gui,
1171 start_improv,
1172 stop,
1173+ WEB_PORT,
1174 )
1175 # Import the whole utils package for monkey patching.
1176 import utils
1177@@ -261,6 +269,49 @@
1178 self.assertEqual(self.zookeeper_address, address)
1179
1180
1181+class LogHookTest(unittest.TestCase):
1182+
1183+ def setUp(self):
1184+ # Monkeypatch the charmhelpers log function.
1185+ self.output = []
1186+ self.original = utils.log
1187+ utils.log = self.output.append
1188+
1189+ def tearDown(self):
1190+ # Restore the original charmhelpers log function.
1191+ utils.log = self.original
1192+
1193+ def test_logging(self):
1194+ # The function emits log messages on entering and exiting the hook.
1195+ with log_hook():
1196+ self.output.append('executing hook')
1197+ self.assertEqual(3, len(self.output))
1198+ enter_message, executing_message, exit_message = self.output
1199+ self.assertIn('>>> Entering', enter_message)
1200+ self.assertEqual('executing hook', executing_message)
1201+ self.assertIn('<<< Exiting', exit_message)
1202+
1203+ def test_subprocess_error(self):
1204+ # If a CalledProcessError exception is raised, the command output is
1205+ # logged.
1206+ with self.assertRaises(CalledProcessError) as cm:
1207+ with log_hook():
1208+ raise CalledProcessError(2, 'command', 'output')
1209+ exception = cm.exception
1210+ self.assertIsInstance(exception, CalledProcessError)
1211+ self.assertEqual(2, exception.returncode)
1212+ self.assertEqual('output', self.output[-2])
1213+
1214+ def test_error(self):
1215+ # Possible errors are re-raised by the context manager.
1216+ with self.assertRaises(TypeError) as cm:
1217+ with log_hook():
1218+ raise TypeError
1219+ exception = cm.exception
1220+ self.assertIsInstance(exception, TypeError)
1221+ self.assertIn('<<< Exiting', self.output[-1])
1222+
1223+
1224 class ParseSourceTest(unittest.TestCase):
1225
1226 def test_latest_stable_release(self):
1227@@ -295,9 +346,9 @@
1228 def setUp(self):
1229 self.destination_file = tempfile.NamedTemporaryFile()
1230 self.addCleanup(self.destination_file.close)
1231- self.template_contents = '%(foo)s, %(bar)s'
1232+ self.template = tempita.Template('{{foo}}, {{bar}}')
1233 with tempfile.NamedTemporaryFile(delete=False) as template_file:
1234- template_file.write(self.template_contents)
1235+ template_file.write(self.template.content)
1236 self.template_path = template_file.name
1237 self.addCleanup(os.remove, self.template_path)
1238
1239@@ -305,7 +356,7 @@
1240 # Ensure the template is correctly rendered using the given context.
1241 context = {'foo': 'spam', 'bar': 'eggs'}
1242 render_to_file(self.template_path, context, self.destination_file.name)
1243- expected = self.template_contents % context
1244+ expected = self.template.substitute(context)
1245 self.assertEqual(expected, self.destination_file.read())
1246
1247
1248@@ -331,6 +382,12 @@
1249 self.assertIn('mycert', open(self.cert_file).read())
1250 self.assertIn('mykey', open(self.key_file).read())
1251
1252+ def test_pem_file(self):
1253+ # Ensure the pem file is created concatenating the key and cert files.
1254+ save_or_create_certificates(self.cert_path, 'Certificate', 'Key')
1255+ pem_file = os.path.join(self.cert_path, JUJU_PEM)
1256+ self.assertEqual('KeyCertificate', open(pem_file).read())
1257+
1258
1259 class CmdLogTest(unittest.TestCase):
1260 def setUp(self):
1261@@ -401,12 +458,11 @@
1262 charmhelpers.command = self.command
1263
1264 def test_start_improv(self):
1265- port = '1234'
1266 staging_env = 'large'
1267- start_improv(port, staging_env, self.ssl_cert_path,
1268- self.destination_file.name)
1269+ start_improv(
1270+ staging_env, self.ssl_cert_path, self.destination_file.name)
1271 conf = self.destination_file.read()
1272- self.assertTrue('--port %s' % port in conf)
1273+ self.assertTrue('--port %s' % API_PORT in conf)
1274 self.assertTrue(staging_env + '.json' in conf)
1275 self.assertTrue(self.ssl_cert_path in conf)
1276 self.assertEqual(self.svc_ctl_call_count, 1)
1277@@ -414,10 +470,9 @@
1278 self.assertEqual(self.actions, [charmhelpers.START])
1279
1280 def test_start_agent(self):
1281- port = '1234'
1282- start_agent(port, self.ssl_cert_path, self.destination_file.name)
1283+ start_agent(self.ssl_cert_path, self.destination_file.name)
1284 conf = self.destination_file.read()
1285- self.assertTrue('--port %s' % port in conf)
1286+ self.assertTrue('--port %s' % API_PORT in conf)
1287 self.assertTrue('JUJU_ZOOKEEPER=%s' % self.fake_zk_address in conf)
1288 self.assertTrue(self.ssl_cert_path in conf)
1289 self.assertEqual(self.svc_ctl_call_count, 1)
1290@@ -425,41 +480,71 @@
1291 self.assertEqual(self.actions, [charmhelpers.START])
1292
1293 def test_start_gui(self):
1294- port = '1234'
1295+ config_js_file = self.destination_file
1296+ haproxy_file = tempfile.NamedTemporaryFile()
1297+ self.addCleanup(haproxy_file.close)
1298 nginx_file = tempfile.NamedTemporaryFile()
1299 self.addCleanup(nginx_file.close)
1300- config_js_file = tempfile.NamedTemporaryFile()
1301- self.addCleanup(config_js_file.close)
1302+ ssl_cert_path = '/tmp/certificates/'
1303 start_gui(
1304- port, False, 'This is login help.', True, True,
1305- '/tmp/certificates/', self.destination_file.name, nginx_file.name,
1306- config_js_file.name)
1307- conf = self.destination_file.read()
1308- self.assertTrue('/usr/sbin/nginx' in conf)
1309+ False, 'This is login help.', True, True, ssl_cert_path, True,
1310+ haproxy_path=haproxy_file.name, nginx_path=nginx_file.name,
1311+ config_js_path=config_js_file.name)
1312+ self.assertEqual(self.svc_ctl_call_count, 2)
1313+ self.assertEqual(self.service_names, ['nginx', 'haproxy'])
1314+ self.assertEqual(self.actions, [charmhelpers.START] * 2)
1315+ haproxy_conf = haproxy_file.read()
1316+ self.assertIn('ca-base {0}'.format(ssl_cert_path), haproxy_conf)
1317+ self.assertIn('crt-base {0}'.format(ssl_cert_path), haproxy_conf)
1318+ self.assertIn('ws1 127.0.0.1:{0}'.format(API_PORT), haproxy_conf)
1319+ self.assertIn('web1 127.0.0.1:{0}'.format(WEB_PORT), haproxy_conf)
1320+ self.assertIn('ca-file {0}'.format(JUJU_PEM), haproxy_conf)
1321+ self.assertIn('crt {0}'.format(JUJU_PEM), haproxy_conf)
1322+ self.assertIn('redirect scheme https', haproxy_conf)
1323 js_conf = config_js_file.read()
1324+ self.assertIn('consoleEnabled: false', js_conf)
1325 self.assertIn('user: "admin"', js_conf)
1326 self.assertIn('password: "admin"', js_conf)
1327 self.assertIn('login_help: "This is login help."', js_conf)
1328 self.assertIn('readOnly: true', js_conf)
1329+ self.assertIn("socket_url: 'wss://", js_conf)
1330+ self.assertIn('socket_protocol: "wss"', js_conf)
1331 nginx_conf = nginx_file.read()
1332- self.assertTrue('juju-gui/build-debug' in nginx_conf)
1333- self.assertIn('/tmp/certificates/juju.crt', nginx_conf)
1334- self.assertIn('/tmp/certificates/juju.key', nginx_conf)
1335- self.assertEqual(self.svc_ctl_call_count, 1)
1336- self.assertEqual(self.service_names, ['juju-gui'])
1337- self.assertEqual(self.actions, [charmhelpers.START])
1338+ self.assertIn('juju-gui/build-', nginx_conf)
1339+ self.assertIn('listen 127.0.0.1:{0}'.format(WEB_PORT), nginx_conf)
1340+ self.assertIn('alias {0}/test/;'.format(JUJU_GUI_DIR), nginx_conf)
1341+
1342+ def test_start_gui_insecure(self):
1343+ config_js_file = self.destination_file
1344+ haproxy_file = tempfile.NamedTemporaryFile()
1345+ self.addCleanup(haproxy_file.close)
1346+ nginx_file = tempfile.NamedTemporaryFile()
1347+ self.addCleanup(nginx_file.close)
1348+ ssl_cert_path = '/tmp/certificates/'
1349+ start_gui(
1350+ False, 'This is login help.', True, True, ssl_cert_path, True,
1351+ haproxy_path=haproxy_file.name, nginx_path=nginx_file.name,
1352+ config_js_path=config_js_file.name, secure=False)
1353+ js_conf = config_js_file.read()
1354+ self.assertIn("socket_url: 'ws://", js_conf)
1355+ self.assertIn('socket_protocol: "ws"', js_conf)
1356+ haproxy_conf = haproxy_file.read()
1357+ # The insecure approach eliminates the https redirect.
1358+ self.assertNotIn('redirect scheme https', haproxy_conf)
1359
1360 def test_stop_staging(self):
1361 stop(True)
1362- self.assertEqual(self.svc_ctl_call_count, 2)
1363- self.assertEqual(self.service_names, ['juju-gui', 'juju-api-improv'])
1364- self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.STOP])
1365+ self.assertEqual(self.svc_ctl_call_count, 3)
1366+ self.assertEqual(
1367+ self.service_names, ['haproxy', 'nginx', 'juju-api-improv'])
1368+ self.assertEqual(self.actions, [charmhelpers.STOP] * 3)
1369
1370 def test_stop_production(self):
1371 stop(False)
1372- self.assertEqual(self.svc_ctl_call_count, 2)
1373- self.assertEqual(self.service_names, ['juju-gui', 'juju-api-agent'])
1374- self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.STOP])
1375+ self.assertEqual(self.svc_ctl_call_count, 3)
1376+ self.assertEqual(
1377+ self.service_names, ['haproxy', 'nginx', 'juju-api-agent'])
1378+ self.assertEqual(self.actions, [charmhelpers.STOP] * 3)
1379
1380
1381 if __name__ == '__main__':

Subscribers

People subscribed via source and target branches

to all changes: