Merge lp:~bloodearnest/charms/precise/gunicorn/upgrade-path into lp:~charmers/charms/precise/gunicorn/trunk

Proposed by Simon Davy
Status: Merged
Merged at revision: 30
Proposed branch: lp:~bloodearnest/charms/precise/gunicorn/upgrade-path
Merge into: lp:~charmers/charms/precise/gunicorn/trunk
Diff against target: 159 lines (+76/-11)
3 files modified
hooks/hooks.py (+49/-8)
hooks/tests/test_hooks.py (+26/-2)
revision (+1/-1)
To merge this branch: bzr merge lp:~bloodearnest/charms/precise/gunicorn/upgrade-path
Reviewer Review Type Date Requested Status
Matt Bruzek (community) Approve
Wes Mason (community) Approve
Michael Nelson (community) Approve
Review via email: mp+213236@code.launchpad.net

Commit message

Allow an easier upgrade path from old bash version of charm by adding support for old env_extra relation data format, and removing old service. Plus, and fix bug/wart in config-changed hook.

Description of the change

add support for old env_extra relation data format, to allow an upgrade path.

The old format was a partial python dict format, e.g.

'A': 1, 'B': 'C'

The new format is standard env format, e.g

A=1 B="C"

This update adds support back for the old format, to allow an upgrade path, as it's impossible to upgrade a subordinate and its principle in lock-step.

It also removes the older-style gunicorn config and service from previous versions of the charm, if found.

Also, using remote_unit as job name doesn't work well in config-changed hooks, so switched to local_unit, as avoids unnecessary complications.

To post a comment you must log in.
32. By Simon Davy

catch additional missing exception

Revision history for this message
Michael Nelson (michael.nelson) wrote :

I ran into the issue related to remote_unit. Updated to this branch to fix that. Everything works except that I had to manually `juju ssh my-service/0 "sudo service gunicorn stop" (but that might just be related to the *very* old gunicorn charm I was using.

review: Approve
33. By Simon Davy

remove old gunicorn config if exists

Revision history for this message
Simon Davy (bloodearnest) wrote :

I had added code to remove the old non-upstart service on upgrade, if it exists.

34. By Simon Davy

bump to revision 4

Revision history for this message
Wes Mason (wesmason) :
review: Approve
35. By Simon Davy

fix string/int issue for wsgi_worker config

Revision history for this message
Simon Davy (bloodearnest) wrote :
36. By Simon Davy

better fix for wsgi_workers quoting issue

Revision history for this message
Matt Bruzek (mbruzek) wrote :

+1 LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/hooks.py'
2--- hooks/hooks.py 2014-02-27 14:54:18 +0000
3+++ hooks/hooks.py 2014-04-22 13:27:23 +0000
4@@ -1,10 +1,12 @@
5 #!/usr/bin/env python
6 # vim: et ai ts=4 sw=4:
7
8+import ast
9 import os
10 import sys
11 from multiprocessing import cpu_count
12 import shlex
13+import glob
14
15 from charmhelpers.core import hookenv
16 from charmhelpers.core import host
17@@ -30,7 +32,7 @@
18
19
20 def sanitized_service_name():
21- return sanitize(hookenv.remote_unit().split('/')[0])
22+ return sanitize(hookenv.local_unit().split('/')[0])
23
24
25 def upstart_conf_path(name):
26@@ -55,13 +57,39 @@
27 # Hook functions
28 ###############################################################################
29
30-
31-@hooks.hook('install', 'upgrade-charm')
32-def install():
33+def ensure_packages():
34 fetch.apt_update()
35 fetch.apt_install(CHARM_PACKAGES)
36
37
38+@hooks.hook('install')
39+def install():
40+ ensure_packages()
41+
42+
43+@hooks.hook('upgrade-charm')
44+def upgrade():
45+ ensure_packages()
46+ # if we are upgrading from older charm that used gunicorn runner rather
47+ # than upstart, remove that job/config
48+ # sadly, we don't 100% know the name of the job, as it depends on the
49+ # remote unit name, which we don't know in upgrade-charm hook
50+ # TODO: remove this at somepoint
51+ files = glob.glob('/etc/gunicorn.d/*.conf')
52+ if files:
53+ try:
54+ hookenv.log('stopping system gunicorn service')
55+ host.service_stop('gunicorn')
56+ except:
57+ pass
58+ for file in files:
59+ try:
60+ hookenv.log('removing old guncorn config: %s' % file)
61+ os.remove(file)
62+ except:
63+ pass
64+
65+
66 @hooks.hook(
67 "config-changed",
68 "wsgi-file-relation-joined",
69@@ -100,13 +128,26 @@
70 elif wsgi_config['wsgi_worker_class'] == 'tornado':
71 fetch.apt_install('python-tornado')
72
73- if wsgi_config['wsgi_workers'] == 0:
74+ if str(wsgi_config['wsgi_workers']) == '0':
75 wsgi_config['wsgi_workers'] = cpu_count() + 1
76
77 env_extra = wsgi_config.get('env_extra', '')
78- wsgi_config['env_extra'] = [
79- v.split('=') for v in shlex.split(env_extra) if '=' in v
80- ]
81+
82+ # support old python dict format for upgrade path
83+ extra = []
84+ # attempt dict parsing
85+ try:
86+ dict_str = '{' + env_extra + '}'
87+ extra = [[k, str(v)] for k, v in ast.literal_eval(dict_str).items()]
88+ except (SyntaxError, ValueError):
89+ pass
90+
91+ if not extra:
92+ extra = [
93+ v.split('=', 1) for v in shlex.split(env_extra) if '=' in v
94+ ]
95+
96+ wsgi_config['env_extra'] = extra
97
98 process_template('upstart.tmpl', wsgi_config, project_conf)
99
100
101=== modified file 'hooks/tests/test_hooks.py'
102--- hooks/tests/test_hooks.py 2014-02-27 14:54:18 +0000
103+++ hooks/tests/test_hooks.py 2014-04-22 13:27:23 +0000
104@@ -87,6 +87,19 @@
105 self.fetch.apt_install.assert_called_once_with(
106 ['gunicorn', 'python-jinja2'])
107
108+ @patch('hooks.glob.glob')
109+ @patch('os.remove')
110+ def test_python_upgrade_hook(self, mock_remove, mock_glob):
111+ path = '/etc/gunicorn.d/unit.conf'
112+ mock_glob.return_value = [path]
113+ hooks.upgrade()
114+ self.assertTrue(self.fetch.apt_update.called)
115+ self.fetch.apt_install.assert_called_once_with(
116+ ['gunicorn', 'python-jinja2'])
117+
118+ self.host.service_stop.assert_called_once_with('gunicorn')
119+ mock_remove.assert_called_once_with(path)
120+
121 def test_default_configure_gunicorn(self):
122 hooks.configure_gunicorn()
123 expected = self.get_default_context()
124@@ -129,6 +142,19 @@
125
126 self.assert_wsgi_config_applied(expected)
127
128+ def test_env_extra_old_style_parsing(self):
129+ self.relation_data['env_extra'] = "'A': '1', 'B': 2"
130+
131+ hooks.configure_gunicorn()
132+
133+ expected = self.get_default_context()
134+ expected['env_extra'] = [
135+ ['A', '1'],
136+ ['B', '2'],
137+ ]
138+
139+ self.assert_wsgi_config_applied(expected)
140+
141 def do_worker_class(self, worker_class):
142 self.relation_data['wsgi_worker_class'] = worker_class
143 hooks.configure_gunicorn()
144@@ -147,8 +173,6 @@
145 def test_configure_worker_class_gevent(self):
146 self.do_worker_class('gevent')
147
148-
149-
150 @patch('hooks.os.remove')
151 def test_wsgi_file_relation_broken(self, remove):
152 hooks.wsgi_file_relation_broken()
153
154=== modified file 'revision'
155--- revision 2013-05-29 18:40:56 +0000
156+++ revision 2014-04-22 13:27:23 +0000
157@@ -1,1 +1,1 @@
158-3
159+4

Subscribers

People subscribed via source and target branches

to all changes: