Merge lp:~corey.bryant/charm-helpers/wsgi into lp:charm-helpers
- wsgi
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 670 |
Proposed branch: | lp:~corey.bryant/charm-helpers/wsgi |
Merge into: | lp:charm-helpers |
Diff against target: |
294 lines (+232/-0) 5 files modified
charmhelpers/contrib/openstack/context.py (+40/-0) charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf (+100/-0) charmhelpers/contrib/openstack/utils.py (+29/-0) tests/contrib/openstack/test_openstack_utils.py (+34/-0) tests/contrib/openstack/test_os_contexts.py (+29/-0) |
To merge this branch: | bzr merge lp:~corey.bryant/charm-helpers/wsgi |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
charmers | Pending | ||
Review via email:
|
Commit message
Add WSGI context and template for OpenStack APIs
Description of the change
- 667. By Liam Young
-
[gnuoy, r=ajkavanagh] Fixes for amulet memcache service checks.
1) Use 'service' not 'status' for checking status of memcached on pre-systemd systems as its not an upstart script.
2) Trusty memcache uses ip6-localhost as its listen address because it does not understand ::1 - 668. By Stuart Bishop
-
[hloeung,r=stub] Add timeout to rsync and capture rsync error messages
- 669. By Liam Young
-
[gnuoy, r=ajkavanagh] Add option package var to Memcache context to allow contect to lookup Openstack release without using Openstack-origin which may not be present. enable_memcache also had to be extended to take a package option to support this
- 670. By Corey Bryant
-
Add WSGI context and template for OpenStack APIs
- 671. By Corey Bryant
-
Add unit test for WSGI context.
- 672. By Corey Bryant
-
Add unit test for git_determine_*()
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Corey Bryant (corey.bryant) wrote : | # |
Thanks for the review Liam. I've added unit tests.
Preview Diff
1 | === modified file 'charmhelpers/contrib/openstack/context.py' | |||
2 | --- charmhelpers/contrib/openstack/context.py 2016-12-14 15:10:32 +0000 | |||
3 | +++ charmhelpers/contrib/openstack/context.py 2016-12-14 18:51:33 +0000 | |||
4 | @@ -14,6 +14,7 @@ | |||
5 | 14 | 14 | ||
6 | 15 | import glob | 15 | import glob |
7 | 16 | import json | 16 | import json |
8 | 17 | import math | ||
9 | 17 | import os | 18 | import os |
10 | 18 | import re | 19 | import re |
11 | 19 | import time | 20 | import time |
12 | @@ -90,6 +91,8 @@ | |||
13 | 90 | from charmhelpers.contrib.openstack.utils import ( | 91 | from charmhelpers.contrib.openstack.utils import ( |
14 | 91 | config_flags_parser, | 92 | config_flags_parser, |
15 | 92 | get_host_ip, | 93 | get_host_ip, |
16 | 94 | git_determine_usr_bin, | ||
17 | 95 | git_determine_python_path, | ||
18 | 93 | enable_memcache, | 96 | enable_memcache, |
19 | 94 | ) | 97 | ) |
20 | 95 | from charmhelpers.core.unitdata import kv | 98 | from charmhelpers.core.unitdata import kv |
21 | @@ -1208,6 +1211,43 @@ | |||
22 | 1208 | return ctxt | 1211 | return ctxt |
23 | 1209 | 1212 | ||
24 | 1210 | 1213 | ||
25 | 1214 | class WSGIWorkerConfigContext(WorkerConfigContext): | ||
26 | 1215 | |||
27 | 1216 | def __init__(self, name=None, script=None, admin_script=None, | ||
28 | 1217 | public_script=None, process_weight=1.00, | ||
29 | 1218 | admin_process_weight=0.75, public_process_weight=0.25): | ||
30 | 1219 | self.service_name = name | ||
31 | 1220 | self.user = name | ||
32 | 1221 | self.group = name | ||
33 | 1222 | self.script = script | ||
34 | 1223 | self.admin_script = admin_script | ||
35 | 1224 | self.public_script = public_script | ||
36 | 1225 | self.process_weight = process_weight | ||
37 | 1226 | self.admin_process_weight = admin_process_weight | ||
38 | 1227 | self.public_process_weight = public_process_weight | ||
39 | 1228 | |||
40 | 1229 | def __call__(self): | ||
41 | 1230 | multiplier = config('worker-multiplier') or 1 | ||
42 | 1231 | total_processes = self.num_cpus * multiplier | ||
43 | 1232 | ctxt = { | ||
44 | 1233 | "service_name": self.service_name, | ||
45 | 1234 | "user": self.user, | ||
46 | 1235 | "group": self.group, | ||
47 | 1236 | "script": self.script, | ||
48 | 1237 | "admin_script": self.admin_script, | ||
49 | 1238 | "public_script": self.public_script, | ||
50 | 1239 | "processes": int(math.ceil(self.process_weight * total_processes)), | ||
51 | 1240 | "admin_processes": int(math.ceil(self.admin_process_weight * | ||
52 | 1241 | total_processes)), | ||
53 | 1242 | "public_processes": int(math.ceil(self.public_process_weight * | ||
54 | 1243 | total_processes)), | ||
55 | 1244 | "threads": 1, | ||
56 | 1245 | "usr_bin": git_determine_usr_bin(), | ||
57 | 1246 | "python_path": git_determine_python_path(), | ||
58 | 1247 | } | ||
59 | 1248 | return ctxt | ||
60 | 1249 | |||
61 | 1250 | |||
62 | 1211 | class ZeroMQContext(OSContextGenerator): | 1251 | class ZeroMQContext(OSContextGenerator): |
63 | 1212 | interfaces = ['zeromq-configuration'] | 1252 | interfaces = ['zeromq-configuration'] |
64 | 1213 | 1253 | ||
65 | 1214 | 1254 | ||
66 | === added file 'charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf' | |||
67 | --- charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf 1970-01-01 00:00:00 +0000 | |||
68 | +++ charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf 2016-12-14 18:51:33 +0000 | |||
69 | @@ -0,0 +1,100 @@ | |||
70 | 1 | # Configuration file maintained by Juju. Local changes may be overwritten. | ||
71 | 2 | |||
72 | 3 | {% if port -%} | ||
73 | 4 | Listen {{ port }} | ||
74 | 5 | {% endif -%} | ||
75 | 6 | |||
76 | 7 | {% if admin_port -%} | ||
77 | 8 | Listen {{ admin_port }} | ||
78 | 9 | {% endif -%} | ||
79 | 10 | |||
80 | 11 | {% if public_port -%} | ||
81 | 12 | Listen {{ public_port }} | ||
82 | 13 | {% endif -%} | ||
83 | 14 | |||
84 | 15 | {% if port -%} | ||
85 | 16 | <VirtualHost *:{{ port }}> | ||
86 | 17 | WSGIDaemonProcess {{ service_name }} processes={{ processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \ | ||
87 | 18 | {% if python_path -%} | ||
88 | 19 | python-path={{ python_path }} \ | ||
89 | 20 | {% endif -%} | ||
90 | 21 | display-name=%{GROUP} | ||
91 | 22 | WSGIProcessGroup {{ service_name }} | ||
92 | 23 | WSGIScriptAlias / {{ script }} | ||
93 | 24 | WSGIApplicationGroup %{GLOBAL} | ||
94 | 25 | WSGIPassAuthorization On | ||
95 | 26 | <IfVersion >= 2.4> | ||
96 | 27 | ErrorLogFormat "%{cu}t %M" | ||
97 | 28 | </IfVersion> | ||
98 | 29 | ErrorLog /var/log/apache2/{{ service_name }}_error.log | ||
99 | 30 | CustomLog /var/log/apache2/{{ service_name }}_access.log combined | ||
100 | 31 | |||
101 | 32 | <Directory {{ usr_bin }}> | ||
102 | 33 | <IfVersion >= 2.4> | ||
103 | 34 | Require all granted | ||
104 | 35 | </IfVersion> | ||
105 | 36 | <IfVersion < 2.4> | ||
106 | 37 | Order allow,deny | ||
107 | 38 | Allow from all | ||
108 | 39 | </IfVersion> | ||
109 | 40 | </Directory> | ||
110 | 41 | </VirtualHost> | ||
111 | 42 | {% endif -%} | ||
112 | 43 | |||
113 | 44 | {% if admin_port -%} | ||
114 | 45 | <VirtualHost *:{{ admin_port }}> | ||
115 | 46 | WSGIDaemonProcess {{ service_name }}-admin processes={{ admin_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \ | ||
116 | 47 | {% if python_path -%} | ||
117 | 48 | python-path={{ python_path }} \ | ||
118 | 49 | {% endif -%} | ||
119 | 50 | display-name=%{GROUP} | ||
120 | 51 | WSGIProcessGroup {{ service_name }}-admin | ||
121 | 52 | WSGIScriptAlias / {{ admin_script }} | ||
122 | 53 | WSGIApplicationGroup %{GLOBAL} | ||
123 | 54 | WSGIPassAuthorization On | ||
124 | 55 | <IfVersion >= 2.4> | ||
125 | 56 | ErrorLogFormat "%{cu}t %M" | ||
126 | 57 | </IfVersion> | ||
127 | 58 | ErrorLog /var/log/apache2/{{ service_name }}_error.log | ||
128 | 59 | CustomLog /var/log/apache2/{{ service_name }}_access.log combined | ||
129 | 60 | |||
130 | 61 | <Directory {{ usr_bin }}> | ||
131 | 62 | <IfVersion >= 2.4> | ||
132 | 63 | Require all granted | ||
133 | 64 | </IfVersion> | ||
134 | 65 | <IfVersion < 2.4> | ||
135 | 66 | Order allow,deny | ||
136 | 67 | Allow from all | ||
137 | 68 | </IfVersion> | ||
138 | 69 | </Directory> | ||
139 | 70 | </VirtualHost> | ||
140 | 71 | {% endif -%} | ||
141 | 72 | |||
142 | 73 | {% if public_port -%} | ||
143 | 74 | <VirtualHost *:{{ public_port }}> | ||
144 | 75 | WSGIDaemonProcess {{ service_name }}-public processes={{ public_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \ | ||
145 | 76 | {% if python_path -%} | ||
146 | 77 | python-path={{ python_path }} \ | ||
147 | 78 | {% endif -%} | ||
148 | 79 | display-name=%{GROUP} | ||
149 | 80 | WSGIProcessGroup {{ service_name }}-public | ||
150 | 81 | WSGIScriptAlias / {{ public_script }} | ||
151 | 82 | WSGIApplicationGroup %{GLOBAL} | ||
152 | 83 | WSGIPassAuthorization On | ||
153 | 84 | <IfVersion >= 2.4> | ||
154 | 85 | ErrorLogFormat "%{cu}t %M" | ||
155 | 86 | </IfVersion> | ||
156 | 87 | ErrorLog /var/log/apache2/{{ service_name }}_error.log | ||
157 | 88 | CustomLog /var/log/apache2/{{ service_name }}_access.log combined | ||
158 | 89 | |||
159 | 90 | <Directory {{ usr_bin }}> | ||
160 | 91 | <IfVersion >= 2.4> | ||
161 | 92 | Require all granted | ||
162 | 93 | </IfVersion> | ||
163 | 94 | <IfVersion < 2.4> | ||
164 | 95 | Order allow,deny | ||
165 | 96 | Allow from all | ||
166 | 97 | </IfVersion> | ||
167 | 98 | </Directory> | ||
168 | 99 | </VirtualHost> | ||
169 | 100 | {% endif -%} | ||
170 | 0 | 101 | ||
171 | === modified file 'charmhelpers/contrib/openstack/utils.py' | |||
172 | --- charmhelpers/contrib/openstack/utils.py 2016-12-14 15:46:41 +0000 | |||
173 | +++ charmhelpers/contrib/openstack/utils.py 2016-12-14 18:51:33 +0000 | |||
174 | @@ -1119,6 +1119,35 @@ | |||
175 | 1119 | shutil.copyfile(service_source, service_dest) | 1119 | shutil.copyfile(service_source, service_dest) |
176 | 1120 | 1120 | ||
177 | 1121 | 1121 | ||
178 | 1122 | def git_determine_usr_bin(): | ||
179 | 1123 | """Return the /usr/bin path for Apache2 config. | ||
180 | 1124 | |||
181 | 1125 | The /usr/bin path will be located in the virtualenv if the charm | ||
182 | 1126 | is configured to deploy from source. | ||
183 | 1127 | """ | ||
184 | 1128 | if git_install_requested(): | ||
185 | 1129 | projects_yaml = config('openstack-origin-git') | ||
186 | 1130 | projects_yaml = git_default_repos(projects_yaml) | ||
187 | 1131 | return os.path.join(git_pip_venv_dir(projects_yaml), 'bin') | ||
188 | 1132 | else: | ||
189 | 1133 | return '/usr/bin' | ||
190 | 1134 | |||
191 | 1135 | |||
192 | 1136 | def git_determine_python_path(): | ||
193 | 1137 | """Return the python-path for Apache2 config. | ||
194 | 1138 | |||
195 | 1139 | Returns 'None' unless the charm is configured to deploy from source, | ||
196 | 1140 | in which case the path of the virtualenv's site-packages is returned. | ||
197 | 1141 | """ | ||
198 | 1142 | if git_install_requested(): | ||
199 | 1143 | projects_yaml = config('openstack-origin-git') | ||
200 | 1144 | projects_yaml = git_default_repos(projects_yaml) | ||
201 | 1145 | return os.path.join(git_pip_venv_dir(projects_yaml), | ||
202 | 1146 | 'lib/python2.7/site-packages') | ||
203 | 1147 | else: | ||
204 | 1148 | return None | ||
205 | 1149 | |||
206 | 1150 | |||
207 | 1122 | def os_workload_status(configs, required_interfaces, charm_func=None): | 1151 | def os_workload_status(configs, required_interfaces, charm_func=None): |
208 | 1123 | """ | 1152 | """ |
209 | 1124 | Decorator to set workload status based on complete contexts | 1153 | Decorator to set workload status based on complete contexts |
210 | 1125 | 1154 | ||
211 | === modified file 'tests/contrib/openstack/test_openstack_utils.py' | |||
212 | --- tests/contrib/openstack/test_openstack_utils.py 2016-09-16 09:50:01 +0000 | |||
213 | +++ tests/contrib/openstack/test_openstack_utils.py 2016-12-14 18:51:33 +0000 | |||
214 | @@ -933,6 +933,40 @@ | |||
215 | 933 | openstack.git_src_dir(openstack_origin_git, 'keystone') | 933 | openstack.git_src_dir(openstack_origin_git, 'keystone') |
216 | 934 | join.assert_called_with('/mnt/openstack-git', 'keystone') | 934 | join.assert_called_with('/mnt/openstack-git', 'keystone') |
217 | 935 | 935 | ||
218 | 936 | @patch.object(openstack, 'config') | ||
219 | 937 | def test_git_determine_usr_bin(self, config): | ||
220 | 938 | config.return_value = None | ||
221 | 939 | result = openstack.git_determine_usr_bin() | ||
222 | 940 | self.assertEquals(result, '/usr/bin') | ||
223 | 941 | |||
224 | 942 | @patch('os.path.join') | ||
225 | 943 | @patch.object(openstack, 'git_default_repos') | ||
226 | 944 | @patch.object(openstack, 'config') | ||
227 | 945 | def test_git_determine_usr_bin_git(self, config, git_default, join): | ||
228 | 946 | venv_bin = '/mnt/openstack/.venv/bin' | ||
229 | 947 | join.return_value = venv_bin | ||
230 | 948 | config.return_value = openstack_origin_git | ||
231 | 949 | git_default.return_value = openstack_origin_git | ||
232 | 950 | result = openstack.git_determine_usr_bin() | ||
233 | 951 | self.assertEquals(result, venv_bin) | ||
234 | 952 | |||
235 | 953 | @patch.object(openstack, 'config') | ||
236 | 954 | def test_git_determine_python_path(self, config): | ||
237 | 955 | config.return_value = None | ||
238 | 956 | result = openstack.git_determine_python_path() | ||
239 | 957 | self.assertEquals(result, None) | ||
240 | 958 | |||
241 | 959 | @patch('os.path.join') | ||
242 | 960 | @patch.object(openstack, 'git_default_repos') | ||
243 | 961 | @patch.object(openstack, 'config') | ||
244 | 962 | def test_git_determine_python_path_git(self, config, git_default, join): | ||
245 | 963 | venv_site = '/mnt/openstack/.venv/lib/python2.7/site-packages' | ||
246 | 964 | join.return_value = venv_site | ||
247 | 965 | config.return_value = openstack_origin_git | ||
248 | 966 | git_default.return_value = openstack_origin_git | ||
249 | 967 | result = openstack.git_determine_python_path() | ||
250 | 968 | self.assertEquals(result, venv_site) | ||
251 | 969 | |||
252 | 936 | def test_incomplete_relation_data(self): | 970 | def test_incomplete_relation_data(self): |
253 | 937 | configs = MagicMock() | 971 | configs = MagicMock() |
254 | 938 | configs.complete_contexts.return_value = ['pgsql-db', 'amqp'] | 972 | configs.complete_contexts.return_value = ['pgsql-db', 'amqp'] |
255 | 939 | 973 | ||
256 | === modified file 'tests/contrib/openstack/test_os_contexts.py' | |||
257 | --- tests/contrib/openstack/test_os_contexts.py 2016-12-09 16:48:53 +0000 | |||
258 | +++ tests/contrib/openstack/test_os_contexts.py 2016-12-14 18:51:33 +0000 | |||
259 | @@ -2371,6 +2371,35 @@ | |||
260 | 2371 | } | 2371 | } |
261 | 2372 | self.assertEquals(result, expected) | 2372 | self.assertEquals(result, expected) |
262 | 2373 | 2373 | ||
263 | 2374 | @patch.object(context, 'psutil') | ||
264 | 2375 | @patch.object(context, 'git_determine_python_path') | ||
265 | 2376 | @patch.object(context, 'git_determine_usr_bin') | ||
266 | 2377 | def test_wsgi_worker_config_context(self, usr_bin, python_path, psutil): | ||
267 | 2378 | self.config.return_value = 2 # worker-multiplier=2 | ||
268 | 2379 | usr_bin_path = '/usr/bin' | ||
269 | 2380 | usr_bin.return_value = usr_bin_path | ||
270 | 2381 | python_path.return_value = None | ||
271 | 2382 | psutil.cpu_count.return_value = 4 | ||
272 | 2383 | service_name = 'service-name' | ||
273 | 2384 | script = '/usr/bin/script' | ||
274 | 2385 | ctxt = context.WSGIWorkerConfigContext(name=service_name, | ||
275 | 2386 | script=script) | ||
276 | 2387 | expect = { | ||
277 | 2388 | "service_name": service_name, | ||
278 | 2389 | "user": service_name, | ||
279 | 2390 | "group": service_name, | ||
280 | 2391 | "script": script, | ||
281 | 2392 | "admin_script": None, | ||
282 | 2393 | "public_script": None, | ||
283 | 2394 | "processes": 8, | ||
284 | 2395 | "admin_processes": 6, | ||
285 | 2396 | "public_processes": 2, | ||
286 | 2397 | "threads": 1, | ||
287 | 2398 | "usr_bin": usr_bin_path, | ||
288 | 2399 | "python_path": None, | ||
289 | 2400 | } | ||
290 | 2401 | self.assertEqual(expect, ctxt()) | ||
291 | 2402 | |||
292 | 2374 | def test_zeromq_context_unrelated(self): | 2403 | def test_zeromq_context_unrelated(self): |
293 | 2375 | self.is_relation_made.return_value = False | 2404 | self.is_relation_made.return_value = False |
294 | 2376 | self.assertEquals(context.ZeroMQContext()(), {}) | 2405 | self.assertEquals(context.ZeroMQContext()(), {}) |
It looks good but needs unit tests for the new context and methods