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