Merge lp:~bac/charms/precise/juju-gui/config-changed into lp:~juju-gui/charms/precise/juju-gui/trunk
- Precise Pangolin (12.04)
- config-changed
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 21 | ||||
Proposed branch: | lp:~bac/charms/precise/juju-gui/config-changed | ||||
Merge into: | lp:~juju-gui/charms/precise/juju-gui/trunk | ||||
Diff against target: |
711 lines (+429/-145) 8 files modified
.bzrignore (+1/-0) hooks/config-changed (+131/-0) hooks/install (+16/-38) hooks/start (+3/-83) hooks/stop (+2/-19) hooks/utils.py (+167/-4) revision (+1/-1) tests/test_utils.py (+108/-0) |
||||
To merge this branch: | bzr merge lp:~bac/charms/precise/juju-gui/config-changed | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nicola Larosa (community) | Approve | ||
Gary Poster (community) | Approve | ||
Review via email: mp+140316@code.launchpad.net |
Commit message
Description of the change
Add support for config-changed
The config-changed hook has been added. In order to share code from the
install hook, common routines were moved to utils. Those routines have unit
tests now, except for 'build' which didn't look like much.
Many of the functions in utils were modified for testability where they needed
to write files.
There is an issue lurking. Using 'juju set juju-gui juju-gui-
branch> seems to leave nginx in an odd state. It is started but it cannot
find the document root so it may be a race condition. I'll continue to hunt
for the problem while this cut is in review.
Gary Poster (gary) wrote : | # |
- 21. By Brad Crittenden
-
Merged Francesco's changes. Cleanup, lint, and test changes after merging with trunk.
Gary Poster (gary) wrote : | # |
Francesco's changes fix the problems you described, and he is not blocked.
Please make a bug/card to at least consider simplifying the install/
Thank you!
Gary
Nicola Larosa (teknico) wrote : | # |
Code looks good to me. Mind you, I only looked at the code, I did not actually run it, though.
Preview Diff
1 | === added file '.bzrignore' | |||
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 | |||
3 | +++ .bzrignore 2012-12-18 13:27:18 +0000 | |||
4 | @@ -0,0 +1,1 @@ | |||
5 | 1 | .emacs* | ||
6 | 0 | 2 | ||
7 | === added file 'hooks/config-changed' | |||
8 | --- hooks/config-changed 1970-01-01 00:00:00 +0000 | |||
9 | +++ hooks/config-changed 2012-12-18 13:27:18 +0000 | |||
10 | @@ -0,0 +1,131 @@ | |||
11 | 1 | #!/usr/bin/env python2 | ||
12 | 2 | # -*- python -*- | ||
13 | 3 | |||
14 | 4 | # Copyright 2012 Canonical Ltd. This software is licensed under the | ||
15 | 5 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
16 | 6 | |||
17 | 7 | import os.path | ||
18 | 8 | from shutil import rmtree | ||
19 | 9 | from subprocess import CalledProcessError | ||
20 | 10 | import sys | ||
21 | 11 | |||
22 | 12 | from charmhelpers import ( | ||
23 | 13 | get_config, | ||
24 | 14 | log, | ||
25 | 15 | log_entry, | ||
26 | 16 | log_exit, | ||
27 | 17 | service_control, | ||
28 | 18 | STOP, | ||
29 | 19 | ) | ||
30 | 20 | from shelltoolbox import ( | ||
31 | 21 | DictDiffer, | ||
32 | 22 | su, | ||
33 | 23 | ) | ||
34 | 24 | |||
35 | 25 | from utils import ( | ||
36 | 26 | AGENT, | ||
37 | 27 | build, | ||
38 | 28 | config_json, | ||
39 | 29 | fetch, | ||
40 | 30 | GUI, | ||
41 | 31 | IMPROV, | ||
42 | 32 | start_agent, | ||
43 | 33 | start_gui, | ||
44 | 34 | start_improv, | ||
45 | 35 | ) | ||
46 | 36 | |||
47 | 37 | |||
48 | 38 | def handle_config_changes(config, diff): | ||
49 | 39 | # Handle all configuration file changes. | ||
50 | 40 | log('Updating configuration.') | ||
51 | 41 | |||
52 | 42 | staging = config.get('staging') | ||
53 | 43 | staging_environment = config.get('staging-environment') | ||
54 | 44 | juju_api_port = config.get('juju-api-port') | ||
55 | 45 | added_or_changed = diff.added_or_changed | ||
56 | 46 | |||
57 | 47 | # Fetch new branches? | ||
58 | 48 | gui_branch = None | ||
59 | 49 | api_branch = None | ||
60 | 50 | if 'juju-gui-branch' in added_or_changed: | ||
61 | 51 | if os.path.exists('juju-gui'): | ||
62 | 52 | rmtree('juju-gui') | ||
63 | 53 | gui_branch = config['juju-gui-branch'] | ||
64 | 54 | if 'juju-api-branch' in added_or_changed: | ||
65 | 55 | if os.path.exists('juju'): | ||
66 | 56 | rmtree('juju') | ||
67 | 57 | api_branch = config['juju-api-branch'] | ||
68 | 58 | if gui_branch or api_branch: | ||
69 | 59 | fetch(gui_branch, api_branch) | ||
70 | 60 | build(config['command-log-file']) | ||
71 | 61 | # Restarting of the gui and api services is handled below. | ||
72 | 62 | |||
73 | 63 | # Handle changes to the improv server configuration. | ||
74 | 64 | if staging: | ||
75 | 65 | staging_properties = set( | ||
76 | 66 | ['staging', 'staging-environment', 'juju-api-port']) | ||
77 | 67 | staging_changed = added_or_changed & staging_properties | ||
78 | 68 | if staging_changed or (api_branch is not None): | ||
79 | 69 | if 'staging' in added_or_changed: | ||
80 | 70 | # 'staging' went from False to True, so the agent server is | ||
81 | 71 | # running and must be stopped. | ||
82 | 72 | current_api = AGENT | ||
83 | 73 | else: | ||
84 | 74 | # Only staging parameters changed, so the existing staging | ||
85 | 75 | # server must be stopped and later restarted. | ||
86 | 76 | current_api = IMPROV | ||
87 | 77 | log('Stopping %s.' % current_api) | ||
88 | 78 | service_control(current_api, STOP) | ||
89 | 79 | |||
90 | 80 | # Now the improv server can be cleanly started. | ||
91 | 81 | log('Starting or restarting improv') | ||
92 | 82 | start_improv(juju_api_port, staging_environment) | ||
93 | 83 | else: | ||
94 | 84 | agent_properties = set(['juju-api-port', 'staging']) | ||
95 | 85 | agent_changed = added_or_changed & agent_properties | ||
96 | 86 | if agent_changed or (api_branch is not None): | ||
97 | 87 | if 'staging' in added_or_changed: | ||
98 | 88 | # If 'staging' transitions to False we need to stop the backend | ||
99 | 89 | # and start the agent. | ||
100 | 90 | current_api = IMPROV | ||
101 | 91 | else: | ||
102 | 92 | # The agent is still running but the configuration has been | ||
103 | 93 | # updated -- bounce it. | ||
104 | 94 | current_api = AGENT | ||
105 | 95 | service_control(current_api, STOP) | ||
106 | 96 | start_agent(juju_api_port) | ||
107 | 97 | |||
108 | 98 | # Handle changes to the juju-gui configuration. | ||
109 | 99 | gui_properties = set( | ||
110 | 100 | ['juju-gui-console-enabled', 'juju-api-port', 'staging']) | ||
111 | 101 | gui_changed = added_or_changed & gui_properties | ||
112 | 102 | if gui_changed or (gui_branch is not None): | ||
113 | 103 | with su('root'): | ||
114 | 104 | service_control('nginx', STOP) | ||
115 | 105 | service_control(GUI, STOP) | ||
116 | 106 | console_enabled = config.get('juju-gui-console-enabled') | ||
117 | 107 | start_gui(juju_api_port, console_enabled, staging) | ||
118 | 108 | |||
119 | 109 | |||
120 | 110 | def main(): | ||
121 | 111 | config = get_config() | ||
122 | 112 | prev_config = config_json.get() | ||
123 | 113 | diff = DictDiffer(config, prev_config) | ||
124 | 114 | |||
125 | 115 | if not diff.modified: | ||
126 | 116 | log("No configuration changes, exiting.") | ||
127 | 117 | sys.exit(0) | ||
128 | 118 | handle_config_changes(config, diff) | ||
129 | 119 | config_json.set(config) | ||
130 | 120 | |||
131 | 121 | |||
132 | 122 | if __name__ == '__main__': | ||
133 | 123 | log_entry() | ||
134 | 124 | try: | ||
135 | 125 | main() | ||
136 | 126 | except CalledProcessError as e: | ||
137 | 127 | log('Exception caught:') | ||
138 | 128 | log(e.output) | ||
139 | 129 | raise | ||
140 | 130 | finally: | ||
141 | 131 | log_exit() | ||
142 | 0 | 132 | ||
143 | === modified file 'hooks/install' | |||
144 | --- hooks/install 2012-12-12 17:29:39 +0000 | |||
145 | +++ hooks/install 2012-12-18 13:27:18 +0000 | |||
146 | @@ -1,12 +1,10 @@ | |||
147 | 1 | #!/usr/bin/env python2 | 1 | #!/usr/bin/env python2 |
148 | 2 | # -*- python -*- | 2 | # -*- python -*- |
149 | 3 | 3 | ||
150 | 4 | import os | ||
151 | 5 | from subprocess import ( | 4 | from subprocess import ( |
152 | 6 | CalledProcessError, | 5 | CalledProcessError, |
153 | 7 | check_call, | 6 | check_call, |
154 | 8 | ) | 7 | ) |
155 | 9 | import tempfile | ||
156 | 10 | 8 | ||
157 | 11 | # python-shelltoolbox is installed as a dependency of python-charmhelpers. | 9 | # python-shelltoolbox is installed as a dependency of python-charmhelpers. |
158 | 12 | check_call(['apt-get', 'install', '-y', 'python-charmhelpers']) | 10 | check_call(['apt-get', 'install', '-y', 'python-charmhelpers']) |
159 | @@ -22,53 +20,33 @@ | |||
160 | 22 | ) | 20 | ) |
161 | 23 | from shelltoolbox import ( | 21 | from shelltoolbox import ( |
162 | 24 | apt_get_install, | 22 | apt_get_install, |
163 | 25 | cd, | ||
164 | 26 | command, | ||
165 | 27 | install_extra_repositories, | 23 | install_extra_repositories, |
166 | 28 | run, | ||
167 | 29 | ) | 24 | ) |
168 | 30 | 25 | ||
174 | 31 | from utils import cmd_log | 26 | from utils import ( |
175 | 32 | 27 | build, | |
176 | 33 | 28 | cmd_log, | |
177 | 34 | def fetch(juju_gui_branch, juju_api_branch): | 29 | config_json, |
178 | 35 | """Install required dependencies and retrieve Juju/Juju GUI branches.""" | 30 | fetch, |
179 | 31 | ) | ||
180 | 32 | |||
181 | 33 | |||
182 | 34 | DEB_DEPENDENCIES = ( | ||
183 | 35 | 'bzr', 'imagemagick', 'make', 'nginx', 'nodejs', 'npm', 'zookeeper') | ||
184 | 36 | |||
185 | 37 | |||
186 | 38 | def get_dependencies(): | ||
187 | 36 | log('Installing dependencies.') | 39 | log('Installing dependencies.') |
188 | 37 | cmd_log(install_extra_repositories('ppa:chris-lea/node.js')) | 40 | cmd_log(install_extra_repositories('ppa:chris-lea/node.js')) |
217 | 38 | cmd_log( | 41 | cmd_log(apt_get_install(*DEB_DEPENDENCIES)) |
190 | 39 | apt_get_install('bzr', 'imagemagick', 'make', | ||
191 | 40 | 'nodejs', 'npm', 'zookeeper', 'nginx')) | ||
192 | 41 | log('Retrieving source checkouts.') | ||
193 | 42 | bzr_checkout = command('bzr', 'co', '--lightweight') | ||
194 | 43 | cmd_log(bzr_checkout(juju_gui_branch, 'juju-gui')) | ||
195 | 44 | cmd_log(bzr_checkout(juju_api_branch, 'juju')) | ||
196 | 45 | |||
197 | 46 | |||
198 | 47 | def build(logpath): | ||
199 | 48 | """Set up Juju GUI and nginx.""" | ||
200 | 49 | log('Building Juju GUI.') | ||
201 | 50 | with cd('juju-gui'): | ||
202 | 51 | logdir = os.path.dirname(logpath) | ||
203 | 52 | fd, name = tempfile.mkstemp(prefix='make-', dir=logdir) | ||
204 | 53 | log('Output from "make" sent to', name) | ||
205 | 54 | run('make', stdout=fd, stderr=fd) | ||
206 | 55 | log('Setting up nginx.') | ||
207 | 56 | nginx_default_site = '/etc/nginx/sites-enabled/default' | ||
208 | 57 | juju_gui_site = '/etc/nginx/sites-available/juju-gui' | ||
209 | 58 | if os.path.exists(nginx_default_site): | ||
210 | 59 | os.remove(nginx_default_site) | ||
211 | 60 | if not os.path.exists(juju_gui_site): | ||
212 | 61 | cmd_log(run('touch', juju_gui_site)) | ||
213 | 62 | cmd_log(run('chown', 'ubuntu:', juju_gui_site)) | ||
214 | 63 | cmd_log( | ||
215 | 64 | run('ln', '-s', juju_gui_site, | ||
216 | 65 | '/etc/nginx/sites-enabled/juju-gui')) | ||
218 | 66 | 42 | ||
219 | 67 | 43 | ||
220 | 68 | def main(): | 44 | def main(): |
221 | 69 | config = get_config() | 45 | config = get_config() |
222 | 46 | get_dependencies() | ||
223 | 70 | fetch(config['juju-gui-branch'], config['juju-api-branch']) | 47 | fetch(config['juju-gui-branch'], config['juju-api-branch']) |
224 | 71 | build(config['command-log-file']) | 48 | build(config['command-log-file']) |
225 | 49 | config_json.set(config) | ||
226 | 72 | 50 | ||
227 | 73 | 51 | ||
228 | 74 | if __name__ == '__main__': | 52 | if __name__ == '__main__': |
229 | 75 | 53 | ||
230 | === modified file 'hooks/start' | |||
231 | --- hooks/start 2012-12-12 11:42:12 +0000 | |||
232 | +++ hooks/start 2012-12-18 13:27:18 +0000 | |||
233 | @@ -1,100 +1,20 @@ | |||
234 | 1 | #!/usr/bin/env python2 | 1 | #!/usr/bin/env python2 |
235 | 2 | #-*- python -*- | 2 | #-*- python -*- |
236 | 3 | 3 | ||
237 | 4 | import json | ||
238 | 5 | import os | ||
239 | 6 | |||
240 | 7 | from charmhelpers import ( | 4 | from charmhelpers import ( |
241 | 8 | get_config, | 5 | get_config, |
242 | 9 | log, | 6 | log, |
243 | 10 | log_entry, | 7 | log_entry, |
244 | 11 | log_exit, | 8 | log_exit, |
245 | 12 | open_port, | 9 | open_port, |
246 | 13 | service_control, | ||
247 | 14 | START, | ||
248 | 15 | unit_get, | ||
249 | 16 | ) | ||
250 | 17 | from shelltoolbox import ( | ||
251 | 18 | run, | ||
252 | 19 | su, | ||
253 | 20 | ) | 10 | ) |
254 | 21 | 11 | ||
255 | 22 | from utils import ( | 12 | from utils import ( |
258 | 23 | get_zookeeper_address, | 13 | start_agent, |
259 | 24 | render_to_file, | 14 | start_gui, |
260 | 15 | start_improv, | ||
261 | 25 | ) | 16 | ) |
262 | 26 | 17 | ||
263 | 27 | CURRENT_DIR = os.getcwd() | ||
264 | 28 | JUJU_DIR = os.path.join(CURRENT_DIR, 'juju') | ||
265 | 29 | JUJU_GUI_DIR = os.path.join(CURRENT_DIR, 'juju-gui') | ||
266 | 30 | |||
267 | 31 | |||
268 | 32 | def start_gui(juju_api_port, console_enabled, staging): | ||
269 | 33 | """Set up and start the Juju GUI server.""" | ||
270 | 34 | with su('root'): | ||
271 | 35 | run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR) | ||
272 | 36 | build_dir = JUJU_GUI_DIR + '/build-' | ||
273 | 37 | build_dir += 'debug' if staging else 'prod' | ||
274 | 38 | log('Setting up Juju GUI start up script.') | ||
275 | 39 | render_to_file( | ||
276 | 40 | 'juju-gui.conf.template', {'juju_gui_dir': JUJU_GUI_DIR}, | ||
277 | 41 | '/etc/init/juju-gui.conf') | ||
278 | 42 | log('Generating the Juju GUI configuration file.') | ||
279 | 43 | context = { | ||
280 | 44 | 'address': unit_get('public-address'), | ||
281 | 45 | 'console_enabled': json.dumps(console_enabled), | ||
282 | 46 | 'port': juju_api_port, | ||
283 | 47 | } | ||
284 | 48 | render_to_file( | ||
285 | 49 | 'config.js.template', context, | ||
286 | 50 | os.path.join(build_dir, 'juju-ui', 'assets', 'config.js')) | ||
287 | 51 | log('Generating the nginx site configuration file.') | ||
288 | 52 | context = { | ||
289 | 53 | 'server_root': build_dir | ||
290 | 54 | } | ||
291 | 55 | render_to_file( | ||
292 | 56 | 'nginx.conf.template', context, | ||
293 | 57 | '/etc/nginx/sites-available/juju-gui') | ||
294 | 58 | log('Starting Juju GUI.') | ||
295 | 59 | with su('root'): | ||
296 | 60 | service_control('juju-gui', START) | ||
297 | 61 | |||
298 | 62 | |||
299 | 63 | def start_improv(juju_api_port, staging_env): | ||
300 | 64 | """Start a simulated juju environment using ``improv.py``.""" | ||
301 | 65 | log('Setting up staging start up script.') | ||
302 | 66 | context = { | ||
303 | 67 | 'juju_dir': JUJU_DIR, | ||
304 | 68 | 'port': juju_api_port, | ||
305 | 69 | 'staging_env': staging_env, | ||
306 | 70 | } | ||
307 | 71 | render_to_file( | ||
308 | 72 | 'juju-api-improv.conf.template', context, | ||
309 | 73 | '/etc/init/juju-api-improv.conf') | ||
310 | 74 | log('Starting the staging backend.') | ||
311 | 75 | with su('root'): | ||
312 | 76 | service_control('juju-api-improv', START) | ||
313 | 77 | |||
314 | 78 | |||
315 | 79 | def start_agent(juju_api_port): | ||
316 | 80 | """Start the Juju agent and connect to the current environment.""" | ||
317 | 81 | # Retrieve the Zookeeper address from the start up script. | ||
318 | 82 | unit_dir = os.path.realpath(os.path.join(CURRENT_DIR, '..')) | ||
319 | 83 | agent_file = '/etc/init/juju-{0}.conf'.format(os.path.basename(unit_dir)) | ||
320 | 84 | zookeeper = get_zookeeper_address(agent_file) | ||
321 | 85 | log('Setting up API agent start up script.') | ||
322 | 86 | context = { | ||
323 | 87 | 'juju_dir': JUJU_DIR, | ||
324 | 88 | 'port': juju_api_port, | ||
325 | 89 | 'zookeeper': zookeeper, | ||
326 | 90 | } | ||
327 | 91 | render_to_file( | ||
328 | 92 | 'juju-api-agent.conf.template', context, | ||
329 | 93 | '/etc/init/juju-api-agent.conf') | ||
330 | 94 | log('Starting API agent.') | ||
331 | 95 | with su('root'): | ||
332 | 96 | service_control('juju-api-agent', START) | ||
333 | 97 | |||
334 | 98 | 18 | ||
335 | 99 | def open_ports(juju_api_port): | 19 | def open_ports(juju_api_port): |
336 | 100 | """Expose Juju GUI web server and websocket server ports.""" | 20 | """Expose Juju GUI web server and websocket server ports.""" |
337 | 101 | 21 | ||
338 | === modified file 'hooks/stop' | |||
339 | --- hooks/stop 2012-12-10 14:37:15 +0000 | |||
340 | +++ hooks/stop 2012-12-18 13:27:18 +0000 | |||
341 | @@ -2,28 +2,11 @@ | |||
342 | 2 | #-*- python -*- | 2 | #-*- python -*- |
343 | 3 | 3 | ||
344 | 4 | from charmhelpers import ( | 4 | from charmhelpers import ( |
345 | 5 | get_config, | ||
346 | 6 | log, | ||
347 | 7 | log_entry, | 5 | log_entry, |
348 | 8 | log_exit, | 6 | log_exit, |
349 | 9 | service_control, | ||
350 | 10 | STOP, | ||
351 | 11 | ) | 7 | ) |
367 | 12 | from shelltoolbox import su | 8 | |
368 | 13 | 9 | from utils import stop | |
354 | 14 | |||
355 | 15 | def stop(): | ||
356 | 16 | """Stop the Juju API agent.""" | ||
357 | 17 | config = get_config() | ||
358 | 18 | with su('root'): | ||
359 | 19 | log('Stopping Juju GUI.') | ||
360 | 20 | service_control('juju-gui', STOP) | ||
361 | 21 | if config.get('staging'): | ||
362 | 22 | log('Stopping the staging backend.') | ||
363 | 23 | service_control('juju-api-improv', STOP) | ||
364 | 24 | else: | ||
365 | 25 | log('Stopping API agent.') | ||
366 | 26 | service_control('juju-api-agent', STOP) | ||
369 | 27 | 10 | ||
370 | 28 | 11 | ||
371 | 29 | if __name__ == '__main__': | 12 | if __name__ == '__main__': |
372 | 30 | 13 | ||
373 | === modified file 'hooks/utils.py' | |||
374 | --- hooks/utils.py 2012-12-07 21:16:55 +0000 | |||
375 | +++ hooks/utils.py 2012-12-18 13:27:18 +0000 | |||
376 | @@ -1,10 +1,51 @@ | |||
377 | 1 | """Juju GUI charm utilities.""" | 1 | """Juju GUI charm utilities.""" |
378 | 2 | 2 | ||
379 | 3 | __all__ = [ | ||
380 | 4 | 'AGENT', | ||
381 | 5 | 'build', | ||
382 | 6 | 'cmd_log', | ||
383 | 7 | 'get_zookeeper_address', | ||
384 | 8 | 'GUI', | ||
385 | 9 | 'IMPROV', | ||
386 | 10 | 'render_to_file', | ||
387 | 11 | 'start_agent', | ||
388 | 12 | 'start_gui', | ||
389 | 13 | 'start_improv', | ||
390 | 14 | 'stop', | ||
391 | 15 | ] | ||
392 | 16 | |||
393 | 17 | import json | ||
394 | 3 | import os | 18 | import os |
395 | 4 | import logging | 19 | import logging |
399 | 5 | 20 | import tempfile | |
400 | 6 | from shelltoolbox import search_file | 21 | |
401 | 7 | from charmhelpers import get_config | 22 | from shelltoolbox import ( |
402 | 23 | cd, | ||
403 | 24 | command, | ||
404 | 25 | run, | ||
405 | 26 | search_file, | ||
406 | 27 | Serializer, | ||
407 | 28 | su, | ||
408 | 29 | ) | ||
409 | 30 | from charmhelpers import ( | ||
410 | 31 | get_config, | ||
411 | 32 | log, | ||
412 | 33 | service_control, | ||
413 | 34 | START, | ||
414 | 35 | STOP, | ||
415 | 36 | unit_get, | ||
416 | 37 | ) | ||
417 | 38 | |||
418 | 39 | |||
419 | 40 | AGENT = 'juju-api-agent' | ||
420 | 41 | IMPROV = 'juju-api-improv' | ||
421 | 42 | GUI = 'juju-gui' | ||
422 | 43 | CURRENT_DIR = os.getcwd() | ||
423 | 44 | JUJU_DIR = os.path.join(CURRENT_DIR, 'juju') | ||
424 | 45 | JUJU_GUI_DIR = os.path.join(CURRENT_DIR, 'juju-gui') | ||
425 | 46 | |||
426 | 47 | # Store the configuration from on invocation to the next. | ||
427 | 48 | config_json = Serializer('/tmp/config.json') | ||
428 | 8 | 49 | ||
429 | 9 | 50 | ||
430 | 10 | def get_zookeeper_address(agent_file_path): | 51 | def get_zookeeper_address(agent_file_path): |
431 | @@ -36,6 +77,7 @@ | |||
432 | 36 | 77 | ||
433 | 37 | results_log = None | 78 | results_log = None |
434 | 38 | 79 | ||
435 | 80 | |||
436 | 39 | def _setupLogging(): | 81 | def _setupLogging(): |
437 | 40 | global results_log | 82 | global results_log |
438 | 41 | if results_log is not None: | 83 | if results_log is not None: |
439 | @@ -45,7 +87,7 @@ | |||
440 | 45 | filename=config['command-log-file'], | 87 | filename=config['command-log-file'], |
441 | 46 | level=logging.INFO, | 88 | level=logging.INFO, |
442 | 47 | format="%(asctime)s: %(name)s@%(levelname)s %(message)s") | 89 | format="%(asctime)s: %(name)s@%(levelname)s %(message)s") |
444 | 48 | results_log = logging.getLogger('juju-gui') | 90 | results_log = logging.getLogger(GUI) |
445 | 49 | 91 | ||
446 | 50 | 92 | ||
447 | 51 | def cmd_log(results): | 93 | def cmd_log(results): |
448 | @@ -57,3 +99,124 @@ | |||
449 | 57 | # Since 'results' may be multi-line output, start it on a separate line | 99 | # Since 'results' may be multi-line output, start it on a separate line |
450 | 58 | # from the logger timestamp, etc. | 100 | # from the logger timestamp, etc. |
451 | 59 | results_log.info('\n' + results) | 101 | results_log.info('\n' + results) |
452 | 102 | |||
453 | 103 | |||
454 | 104 | def start_improv(juju_api_port, staging_env, | ||
455 | 105 | config_path='/etc/init/juju-api-improv.conf'): | ||
456 | 106 | """Start a simulated juju environment using ``improv.py``.""" | ||
457 | 107 | log('Setting up staging start up script.') | ||
458 | 108 | context = { | ||
459 | 109 | 'juju_dir': JUJU_DIR, | ||
460 | 110 | 'port': juju_api_port, | ||
461 | 111 | 'staging_env': staging_env, | ||
462 | 112 | } | ||
463 | 113 | render_to_file( | ||
464 | 114 | 'juju-api-improv.conf.template', context, | ||
465 | 115 | config_path) | ||
466 | 116 | log('Starting the staging backend.') | ||
467 | 117 | with su('root'): | ||
468 | 118 | service_control(IMPROV, START) | ||
469 | 119 | |||
470 | 120 | |||
471 | 121 | def start_agent(juju_api_port, config_path='/etc/init/juju-api-agent.conf'): | ||
472 | 122 | """Start the Juju agent and connect to the current environment.""" | ||
473 | 123 | # Retrieve the Zookeeper address from the start up script. | ||
474 | 124 | unit_dir = os.path.realpath(os.path.join(CURRENT_DIR, '..')) | ||
475 | 125 | agent_file = '/etc/init/juju-{0}.conf'.format(os.path.basename(unit_dir)) | ||
476 | 126 | zookeeper = get_zookeeper_address(agent_file) | ||
477 | 127 | log('Setting up API agent start up script.') | ||
478 | 128 | context = { | ||
479 | 129 | 'juju_dir': JUJU_DIR, | ||
480 | 130 | 'port': juju_api_port, | ||
481 | 131 | 'zookeeper': zookeeper, | ||
482 | 132 | } | ||
483 | 133 | render_to_file( | ||
484 | 134 | 'juju-api-agent.conf.template', context, | ||
485 | 135 | config_path) | ||
486 | 136 | log('Starting API agent.') | ||
487 | 137 | with su('root'): | ||
488 | 138 | service_control(AGENT, START) | ||
489 | 139 | |||
490 | 140 | |||
491 | 141 | def start_gui(juju_api_port, console_enabled, staging, | ||
492 | 142 | config_path='/etc/init/juju-gui.conf', | ||
493 | 143 | nginx_path='/etc/nginx/sites-available/juju-gui', | ||
494 | 144 | config_js_path=None): | ||
495 | 145 | """Set up and start the Juju GUI server.""" | ||
496 | 146 | with su('root'): | ||
497 | 147 | run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR) | ||
498 | 148 | build_dir = JUJU_GUI_DIR + '/build-' | ||
499 | 149 | build_dir += 'debug' if staging else 'prod' | ||
500 | 150 | log('Setting up Juju GUI start up script.') | ||
501 | 151 | render_to_file( | ||
502 | 152 | 'juju-gui.conf.template', {}, config_path) | ||
503 | 153 | log('Generating the Juju GUI configuration file.') | ||
504 | 154 | context = { | ||
505 | 155 | 'address': unit_get('public-address'), | ||
506 | 156 | 'console_enabled': json.dumps(console_enabled), | ||
507 | 157 | 'port': juju_api_port, | ||
508 | 158 | } | ||
509 | 159 | if config_js_path is None: | ||
510 | 160 | config_js_path = os.path.join( | ||
511 | 161 | build_dir, 'juju-ui', 'assets', 'config.js') | ||
512 | 162 | render_to_file( | ||
513 | 163 | 'config.js.template', context, | ||
514 | 164 | config_js_path) | ||
515 | 165 | log('Generating the nginx site configuration file.') | ||
516 | 166 | context = { | ||
517 | 167 | 'server_root': build_dir | ||
518 | 168 | } | ||
519 | 169 | render_to_file( | ||
520 | 170 | 'nginx.conf.template', context, nginx_path) | ||
521 | 171 | log('Starting Juju GUI.') | ||
522 | 172 | with su('root'): | ||
523 | 173 | # Stop nginx so it will restart cleanly with the gui. | ||
524 | 174 | service_control('nginx', STOP) | ||
525 | 175 | service_control(GUI, START) | ||
526 | 176 | |||
527 | 177 | |||
528 | 178 | def stop(): | ||
529 | 179 | """Stop the Juju API agent.""" | ||
530 | 180 | config = get_config() | ||
531 | 181 | with su('root'): | ||
532 | 182 | log('Stopping Juju GUI.') | ||
533 | 183 | service_control(GUI, STOP) | ||
534 | 184 | if config.get('staging'): | ||
535 | 185 | log('Stopping the staging backend.') | ||
536 | 186 | service_control(IMPROV, STOP) | ||
537 | 187 | else: | ||
538 | 188 | log('Stopping API agent.') | ||
539 | 189 | service_control(AGENT, STOP) | ||
540 | 190 | |||
541 | 191 | |||
542 | 192 | def fetch(juju_gui_branch, juju_api_branch): | ||
543 | 193 | """Install required dependencies and retrieve Juju/Juju GUI branches.""" | ||
544 | 194 | log('Retrieving source checkouts.') | ||
545 | 195 | bzr_checkout = command('bzr', 'co', '--lightweight') | ||
546 | 196 | if juju_gui_branch is not None: | ||
547 | 197 | cmd_log(run('rm', '-rf', 'juju-gui')) | ||
548 | 198 | cmd_log(bzr_checkout(juju_gui_branch, 'juju-gui')) | ||
549 | 199 | if juju_api_branch is not None: | ||
550 | 200 | cmd_log(run('rm', '-rf', 'juju')) | ||
551 | 201 | cmd_log(bzr_checkout(juju_api_branch, 'juju')) | ||
552 | 202 | |||
553 | 203 | |||
554 | 204 | def build(logpath): | ||
555 | 205 | """Set up Juju GUI and nginx.""" | ||
556 | 206 | log('Building Juju GUI.') | ||
557 | 207 | with cd('juju-gui'): | ||
558 | 208 | logdir = os.path.dirname(logpath) | ||
559 | 209 | fd, name = tempfile.mkstemp(prefix='make-', dir=logdir) | ||
560 | 210 | log('Output from "make" sent to', name) | ||
561 | 211 | run('make', stdout=fd, stderr=fd) | ||
562 | 212 | log('Setting up nginx.') | ||
563 | 213 | nginx_default_site = '/etc/nginx/sites-enabled/default' | ||
564 | 214 | juju_gui_site = '/etc/nginx/sites-available/juju-gui' | ||
565 | 215 | if os.path.exists(nginx_default_site): | ||
566 | 216 | os.remove(nginx_default_site) | ||
567 | 217 | if not os.path.exists(juju_gui_site): | ||
568 | 218 | cmd_log(run('touch', juju_gui_site)) | ||
569 | 219 | cmd_log(run('chown', 'ubuntu:', juju_gui_site)) | ||
570 | 220 | cmd_log( | ||
571 | 221 | run('ln', '-s', juju_gui_site, | ||
572 | 222 | '/etc/nginx/sites-enabled/juju-gui')) | ||
573 | 60 | 223 | ||
574 | === modified file 'revision' | |||
575 | --- revision 2012-12-14 16:48:29 +0000 | |||
576 | +++ revision 2012-12-18 13:27:18 +0000 | |||
577 | @@ -1,1 +1,1 @@ | |||
579 | 1 | 15 | 1 | 16 |
580 | 2 | 2 | ||
581 | === modified file 'tests/test_utils.py' | |||
582 | --- tests/test_utils.py 2012-12-12 17:29:39 +0000 | |||
583 | +++ tests/test_utils.py 2012-12-18 13:27:18 +0000 | |||
584 | @@ -1,5 +1,6 @@ | |||
585 | 1 | #!/usr/bin/env python2 | 1 | #!/usr/bin/env python2 |
586 | 2 | 2 | ||
587 | 3 | from contextlib import contextmanager | ||
588 | 3 | import os | 4 | import os |
589 | 4 | import tempfile | 5 | import tempfile |
590 | 5 | import unittest | 6 | import unittest |
591 | @@ -10,7 +11,13 @@ | |||
592 | 10 | cmd_log, | 11 | cmd_log, |
593 | 11 | get_zookeeper_address, | 12 | get_zookeeper_address, |
594 | 12 | render_to_file, | 13 | render_to_file, |
595 | 14 | start_agent, | ||
596 | 15 | start_gui, | ||
597 | 16 | start_improv, | ||
598 | 17 | stop, | ||
599 | 13 | ) | 18 | ) |
600 | 19 | # Import the whole utils package for monkey patching. | ||
601 | 20 | import utils | ||
602 | 14 | 21 | ||
603 | 15 | 22 | ||
604 | 16 | class GetZookeeperAddressTest(unittest.TestCase): | 23 | class GetZookeeperAddressTest(unittest.TestCase): |
605 | @@ -68,5 +75,106 @@ | |||
606 | 68 | self.assertTrue(line.endswith(': juju-gui@INFO \nfoo\n')) | 75 | self.assertTrue(line.endswith(': juju-gui@INFO \nfoo\n')) |
607 | 69 | 76 | ||
608 | 70 | 77 | ||
609 | 78 | class StartStopTest(unittest.TestCase): | ||
610 | 79 | |||
611 | 80 | def setUp(self): | ||
612 | 81 | self.service_names = [] | ||
613 | 82 | self.actions = [] | ||
614 | 83 | self.svc_ctl_call_count = 0 | ||
615 | 84 | self.fake_zk_address = '192.168.5.26' | ||
616 | 85 | # Monkey patches. | ||
617 | 86 | self.command = charmhelpers.command | ||
618 | 87 | |||
619 | 88 | def service_control_mock(service_name, action): | ||
620 | 89 | self.svc_ctl_call_count += 1 | ||
621 | 90 | self.service_names.append(service_name) | ||
622 | 91 | self.actions.append(action) | ||
623 | 92 | |||
624 | 93 | def noop(*args): | ||
625 | 94 | pass | ||
626 | 95 | |||
627 | 96 | @contextmanager | ||
628 | 97 | def su(user): | ||
629 | 98 | yield None | ||
630 | 99 | |||
631 | 100 | def get_zookeeper_address_mock(fp): | ||
632 | 101 | return self.fake_zk_address | ||
633 | 102 | |||
634 | 103 | self.functions = dict( | ||
635 | 104 | service_control=(utils.service_control, service_control_mock), | ||
636 | 105 | log=(utils.log, noop), | ||
637 | 106 | su=(utils.su, su), | ||
638 | 107 | run=(utils.run, noop), | ||
639 | 108 | unit_get=(utils.unit_get, noop), | ||
640 | 109 | get_zookeeper_address=( | ||
641 | 110 | utils.get_zookeeper_address, get_zookeeper_address_mock) | ||
642 | 111 | ) | ||
643 | 112 | # Apply the patches. | ||
644 | 113 | for fn, fcns in self.functions.items(): | ||
645 | 114 | setattr(utils, fn, fcns[1]) | ||
646 | 115 | |||
647 | 116 | self.destination_file = tempfile.NamedTemporaryFile() | ||
648 | 117 | self.addCleanup(self.destination_file.close) | ||
649 | 118 | |||
650 | 119 | def tearDown(self): | ||
651 | 120 | # Undo all of the monkey patching. | ||
652 | 121 | for fn, fcns in self.functions.items(): | ||
653 | 122 | setattr(utils, fn, fcns[0]) | ||
654 | 123 | charmhelpers.command = self.command | ||
655 | 124 | |||
656 | 125 | def test_start_improv(self): | ||
657 | 126 | port = '1234' | ||
658 | 127 | staging_env = 'large' | ||
659 | 128 | start_improv(port, staging_env, self.destination_file.name) | ||
660 | 129 | conf = self.destination_file.read() | ||
661 | 130 | self.assertTrue('--port %s' % port in conf) | ||
662 | 131 | self.assertTrue(staging_env + '.json' in conf) | ||
663 | 132 | self.assertEqual(self.svc_ctl_call_count, 1) | ||
664 | 133 | self.assertEqual(self.service_names, ['juju-api-improv']) | ||
665 | 134 | self.assertEqual(self.actions, [charmhelpers.START]) | ||
666 | 135 | |||
667 | 136 | def test_start_agent(self): | ||
668 | 137 | port = '1234' | ||
669 | 138 | start_agent(port, self.destination_file.name) | ||
670 | 139 | conf = self.destination_file.read() | ||
671 | 140 | self.assertTrue('--port %s' % port in conf) | ||
672 | 141 | self.assertTrue('JUJU_ZOOKEEPER=%s' % self.fake_zk_address in conf) | ||
673 | 142 | self.assertEqual(self.svc_ctl_call_count, 1) | ||
674 | 143 | self.assertEqual(self.service_names, ['juju-api-agent']) | ||
675 | 144 | self.assertEqual(self.actions, [charmhelpers.START]) | ||
676 | 145 | |||
677 | 146 | def test_start_gui(self): | ||
678 | 147 | port = '1234' | ||
679 | 148 | nginx_file = tempfile.NamedTemporaryFile() | ||
680 | 149 | self.addCleanup(nginx_file.close) | ||
681 | 150 | config_js_file = tempfile.NamedTemporaryFile() | ||
682 | 151 | self.addCleanup(config_js_file.close) | ||
683 | 152 | start_gui(port, False, True, self.destination_file.name, | ||
684 | 153 | nginx_file.name, config_js_file.name) | ||
685 | 154 | conf = self.destination_file.read() | ||
686 | 155 | self.assertTrue('/usr/sbin/nginx' in conf) | ||
687 | 156 | nginx_conf = nginx_file.read() | ||
688 | 157 | self.assertTrue('juju-gui/build-debug' in nginx_conf) | ||
689 | 158 | self.assertEqual(self.svc_ctl_call_count, 2) | ||
690 | 159 | self.assertEqual(self.service_names, ['nginx', 'juju-gui']) | ||
691 | 160 | self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.START]) | ||
692 | 161 | |||
693 | 162 | def test_stop_staging(self): | ||
694 | 163 | mock_config = {'staging': True} | ||
695 | 164 | charmhelpers.command = lambda *args: lambda: dumps(mock_config) | ||
696 | 165 | stop() | ||
697 | 166 | self.assertEqual(self.svc_ctl_call_count, 2) | ||
698 | 167 | self.assertEqual(self.service_names, ['juju-gui', 'juju-api-improv']) | ||
699 | 168 | self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.STOP]) | ||
700 | 169 | |||
701 | 170 | def test_stop_production(self): | ||
702 | 171 | mock_config = {'staging': False} | ||
703 | 172 | charmhelpers.command = lambda *args: lambda: dumps(mock_config) | ||
704 | 173 | stop() | ||
705 | 174 | self.assertEqual(self.svc_ctl_call_count, 2) | ||
706 | 175 | self.assertEqual(self.service_names, ['juju-gui', 'juju-api-agent']) | ||
707 | 176 | self.assertEqual(self.actions, [charmhelpers.STOP, charmhelpers.STOP]) | ||
708 | 177 | |||
709 | 178 | |||
710 | 71 | if __name__ == '__main__': | 179 | if __name__ == '__main__': |
711 | 72 | unittest.main(verbosity=2) | 180 | unittest.main(verbosity=2) |
Thank you, Brad. The code is very clean, the tests are fast, and this is nice functionality.
The lurking issue you mentioned *may* be irrelevant when Francesco starts work on bug 188618.
As I understand it, the config-changed hook is called initially, after install and before start. See Kapil's answer to http:// askubuntu. com/questions/ 82683/what- juju-charm- hooks-are- available- and-what- does-each- one-do and the discussion of config-changed that he links to in https:/ /juju.ubuntu. com/docs/ drafts/ service- config. html#creating- charms . This means that the install hook can sometimes be wildly simple: it should be able to defer all of its work to the config-changed hook. The only trick then, AFAIK, is that config-changed needs to be able to detect if things have been started yet, and not restart if things have started...or, it should embrace making the start hook irrelevant, perhaps, and always start, as you do now. Checking with Kapil or Ben or a charmer about my understanding and related charm best practices would be a reasonable step for all of these observations.
To be honest, my big unhappiness and concern with this branch is that it is blocking Francesco starting cleanly on bug 1088618 tomorrow morning. That is something we must have to stay on schedule. We didn't need the config-changed functionality in order to meet the letter of the law, and as you know, I like to fulfill what we need and *then* back-fill the other bits that we have time for, if we still believe them to be important. I didn't communicate my position on this config-changed fix clearly in that regard, and it's my fault.
There are a number of ways to resolve this, but under the circumstances, I suggest the following. Francesco should start work tomorrow morning with trunk, merged with this branch. He should feel free to make any changes to it that he needs, ignoring my discussion of possible simplification above unless he needs to address it for his goals. He can propose his branch with your branch as a dependency.
You can pair with him to get his branch moving more quickly, if possible. Maybe in your morning you can resolve the answer to the question I raised at the top, and then in the afternoon he can pass the branch to you to take it farther after his EoD.
Francesco, please communicate with Nicola about this situation in case he wants to start bug 1083920: as few changes to the charm as possible during your work will be helpful.
Thanks again, Brad. This is good work. I want it to land, but there's some work to be done on it yet, and I don't want to have it at the expense of our schedule, so let's see if the coordination with Francesco that I propose might work.
Gary