Merge lp:~brad-marshall/charms/trusty/swift-proxy/add-haproxy-nrpe-fix-servicegroups into lp:~openstack-charmers-archive/charms/trusty/swift-proxy/next
- Trusty Tahr (14.04)
- add-haproxy-nrpe-fix-servicegroups
- Merge into next
Proposed by
Brad Marshall
Status: | Merged |
---|---|
Merged at revision: | 81 |
Proposed branch: | lp:~brad-marshall/charms/trusty/swift-proxy/add-haproxy-nrpe-fix-servicegroups |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/swift-proxy/next |
Diff against target: |
1375 lines (+51/-610) 10 files modified
config.yaml (+6/-1) hooks/charmhelpers/contrib/openstack/context.py (+28/-9) hooks/charmhelpers/contrib/openstack/files/__init__.py (+0/-18) hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh (+0/-32) hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh (+0/-30) hooks/charmhelpers/contrib/openstack/templates/zeromq (+14/-0) hooks/charmhelpers/core/strutils.py (+0/-42) hooks/charmhelpers/core/unitdata.py (+0/-477) hooks/swift_hooks.py (+2/-0) tests/basic_deployment.py (+1/-1) |
To merge this branch: | bzr merge lp:~brad-marshall/charms/trusty/swift-proxy/add-haproxy-nrpe-fix-servicegroups |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+250700@code.launchpad.net |
Commit message
Description of the change
Synced charmhelpers, added nagios_servicegroup config option, and added haproxy nrpe checks.
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #2005 swift-proxy-next for brad-marshall mp250700
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #2162 swift-proxy-next for brad-marshall mp250700
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
ERROR subprocess encountered error code 1
make: *** [test] Error 1
Full amulet test output: http://
Build: http://
- 81. By Brad Marshall
-
[bradm] Fixed merge conflicts
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'config.yaml' |
2 | --- config.yaml 2015-02-19 20:31:42 +0000 |
3 | +++ config.yaml 2015-02-25 23:25:31 +0000 |
4 | @@ -214,4 +214,9 @@ |
5 | juju-myservice-0 |
6 | If you're running multiple environments with the same services in them |
7 | this allows you to differentiate between them. |
8 | - |
9 | + nagios_servicegroups: |
10 | + default: "" |
11 | + type: string |
12 | + description: | |
13 | + A comma-separated list of nagios servicegroups. |
14 | + If left empty, the nagios_context will be used as the servicegroup |
15 | |
16 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' |
17 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-01-26 09:45:43 +0000 |
18 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-02-25 23:25:31 +0000 |
19 | @@ -279,9 +279,25 @@ |
20 | class IdentityServiceContext(OSContextGenerator): |
21 | interfaces = ['identity-service'] |
22 | |
23 | + def __init__(self, service=None, service_user=None): |
24 | + self.service = service |
25 | + self.service_user = service_user |
26 | + |
27 | def __call__(self): |
28 | log('Generating template context for identity-service', level=DEBUG) |
29 | ctxt = {} |
30 | + |
31 | + if self.service and self.service_user: |
32 | + # This is required for pki token signing if we don't want /tmp to |
33 | + # be used. |
34 | + cachedir = '/var/cache/%s' % (self.service) |
35 | + if not os.path.isdir(cachedir): |
36 | + log("Creating service cache dir %s" % (cachedir), level=DEBUG) |
37 | + mkdir(path=cachedir, owner=self.service_user, |
38 | + group=self.service_user, perms=0o700) |
39 | + |
40 | + ctxt['signing_dir'] = cachedir |
41 | + |
42 | for rid in relation_ids('identity-service'): |
43 | for unit in related_units(rid): |
44 | rdata = relation_get(rid=rid, unit=unit) |
45 | @@ -291,15 +307,16 @@ |
46 | auth_host = format_ipv6_addr(auth_host) or auth_host |
47 | svc_protocol = rdata.get('service_protocol') or 'http' |
48 | auth_protocol = rdata.get('auth_protocol') or 'http' |
49 | - ctxt = {'service_port': rdata.get('service_port'), |
50 | - 'service_host': serv_host, |
51 | - 'auth_host': auth_host, |
52 | - 'auth_port': rdata.get('auth_port'), |
53 | - 'admin_tenant_name': rdata.get('service_tenant'), |
54 | - 'admin_user': rdata.get('service_username'), |
55 | - 'admin_password': rdata.get('service_password'), |
56 | - 'service_protocol': svc_protocol, |
57 | - 'auth_protocol': auth_protocol} |
58 | + ctxt.update({'service_port': rdata.get('service_port'), |
59 | + 'service_host': serv_host, |
60 | + 'auth_host': auth_host, |
61 | + 'auth_port': rdata.get('auth_port'), |
62 | + 'admin_tenant_name': rdata.get('service_tenant'), |
63 | + 'admin_user': rdata.get('service_username'), |
64 | + 'admin_password': rdata.get('service_password'), |
65 | + 'service_protocol': svc_protocol, |
66 | + 'auth_protocol': auth_protocol}) |
67 | + |
68 | if context_complete(ctxt): |
69 | # NOTE(jamespage) this is required for >= icehouse |
70 | # so a missing value just indicates keystone needs |
71 | @@ -1021,6 +1038,8 @@ |
72 | for unit in related_units(rid): |
73 | ctxt['zmq_nonce'] = relation_get('nonce', unit, rid) |
74 | ctxt['zmq_host'] = relation_get('host', unit, rid) |
75 | + ctxt['zmq_redis_address'] = relation_get( |
76 | + 'zmq_redis_address', unit, rid) |
77 | |
78 | return ctxt |
79 | |
80 | |
81 | === added directory 'hooks/charmhelpers/contrib/openstack/files' |
82 | === removed directory 'hooks/charmhelpers/contrib/openstack/files' |
83 | === added file 'hooks/charmhelpers/contrib/openstack/files/__init__.py' |
84 | --- hooks/charmhelpers/contrib/openstack/files/__init__.py 1970-01-01 00:00:00 +0000 |
85 | +++ hooks/charmhelpers/contrib/openstack/files/__init__.py 2015-02-25 23:25:31 +0000 |
86 | @@ -0,0 +1,18 @@ |
87 | +# Copyright 2014-2015 Canonical Limited. |
88 | +# |
89 | +# This file is part of charm-helpers. |
90 | +# |
91 | +# charm-helpers is free software: you can redistribute it and/or modify |
92 | +# it under the terms of the GNU Lesser General Public License version 3 as |
93 | +# published by the Free Software Foundation. |
94 | +# |
95 | +# charm-helpers is distributed in the hope that it will be useful, |
96 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
97 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
98 | +# GNU Lesser General Public License for more details. |
99 | +# |
100 | +# You should have received a copy of the GNU Lesser General Public License |
101 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
102 | + |
103 | +# dummy __init__.py to fool syncer into thinking this is a syncable python |
104 | +# module |
105 | |
106 | === removed file 'hooks/charmhelpers/contrib/openstack/files/__init__.py' |
107 | --- hooks/charmhelpers/contrib/openstack/files/__init__.py 2015-02-19 20:12:22 +0000 |
108 | +++ hooks/charmhelpers/contrib/openstack/files/__init__.py 1970-01-01 00:00:00 +0000 |
109 | @@ -1,18 +0,0 @@ |
110 | -# Copyright 2014-2015 Canonical Limited. |
111 | -# |
112 | -# This file is part of charm-helpers. |
113 | -# |
114 | -# charm-helpers is free software: you can redistribute it and/or modify |
115 | -# it under the terms of the GNU Lesser General Public License version 3 as |
116 | -# published by the Free Software Foundation. |
117 | -# |
118 | -# charm-helpers is distributed in the hope that it will be useful, |
119 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
120 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
121 | -# GNU Lesser General Public License for more details. |
122 | -# |
123 | -# You should have received a copy of the GNU Lesser General Public License |
124 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
125 | - |
126 | -# dummy __init__.py to fool syncer into thinking this is a syncable python |
127 | -# module |
128 | |
129 | === added file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh' |
130 | --- hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 1970-01-01 00:00:00 +0000 |
131 | +++ hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 2015-02-25 23:25:31 +0000 |
132 | @@ -0,0 +1,32 @@ |
133 | +#!/bin/bash |
134 | +#-------------------------------------------- |
135 | +# This file is managed by Juju |
136 | +#-------------------------------------------- |
137 | +# |
138 | +# Copyright 2009,2012 Canonical Ltd. |
139 | +# Author: Tom Haddon |
140 | + |
141 | +CRITICAL=0 |
142 | +NOTACTIVE='' |
143 | +LOGFILE=/var/log/nagios/check_haproxy.log |
144 | +AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') |
145 | + |
146 | +for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'}); |
147 | +do |
148 | + output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK') |
149 | + if [ $? != 0 ]; then |
150 | + date >> $LOGFILE |
151 | + echo $output >> $LOGFILE |
152 | + /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1 |
153 | + CRITICAL=1 |
154 | + NOTACTIVE="${NOTACTIVE} $appserver" |
155 | + fi |
156 | +done |
157 | + |
158 | +if [ $CRITICAL = 1 ]; then |
159 | + echo "CRITICAL:${NOTACTIVE}" |
160 | + exit 2 |
161 | +fi |
162 | + |
163 | +echo "OK: All haproxy instances looking good" |
164 | +exit 0 |
165 | |
166 | === removed file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh' |
167 | --- hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 2015-02-19 20:12:22 +0000 |
168 | +++ hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 1970-01-01 00:00:00 +0000 |
169 | @@ -1,32 +0,0 @@ |
170 | -#!/bin/bash |
171 | -#-------------------------------------------- |
172 | -# This file is managed by Juju |
173 | -#-------------------------------------------- |
174 | -# |
175 | -# Copyright 2009,2012 Canonical Ltd. |
176 | -# Author: Tom Haddon |
177 | - |
178 | -CRITICAL=0 |
179 | -NOTACTIVE='' |
180 | -LOGFILE=/var/log/nagios/check_haproxy.log |
181 | -AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') |
182 | - |
183 | -for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'}); |
184 | -do |
185 | - output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK') |
186 | - if [ $? != 0 ]; then |
187 | - date >> $LOGFILE |
188 | - echo $output >> $LOGFILE |
189 | - /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1 |
190 | - CRITICAL=1 |
191 | - NOTACTIVE="${NOTACTIVE} $appserver" |
192 | - fi |
193 | -done |
194 | - |
195 | -if [ $CRITICAL = 1 ]; then |
196 | - echo "CRITICAL:${NOTACTIVE}" |
197 | - exit 2 |
198 | -fi |
199 | - |
200 | -echo "OK: All haproxy instances looking good" |
201 | -exit 0 |
202 | |
203 | === added file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh' |
204 | --- hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 1970-01-01 00:00:00 +0000 |
205 | +++ hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 2015-02-25 23:25:31 +0000 |
206 | @@ -0,0 +1,30 @@ |
207 | +#!/bin/bash |
208 | +#-------------------------------------------- |
209 | +# This file is managed by Juju |
210 | +#-------------------------------------------- |
211 | +# |
212 | +# Copyright 2009,2012 Canonical Ltd. |
213 | +# Author: Tom Haddon |
214 | + |
215 | +# These should be config options at some stage |
216 | +CURRQthrsh=0 |
217 | +MAXQthrsh=100 |
218 | + |
219 | +AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') |
220 | + |
221 | +HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v) |
222 | + |
223 | +for BACKEND in $(echo $HAPROXYSTATS| xargs -n1 | grep BACKEND | awk -F , '{print $1}') |
224 | +do |
225 | + CURRQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 3) |
226 | + MAXQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 4) |
227 | + |
228 | + if [[ $CURRQ -gt $CURRQthrsh || $MAXQ -gt $MAXQthrsh ]] ; then |
229 | + echo "CRITICAL: queue depth for $BACKEND - CURRENT:$CURRQ MAX:$MAXQ" |
230 | + exit 2 |
231 | + fi |
232 | +done |
233 | + |
234 | +echo "OK: All haproxy queue depths looking good" |
235 | +exit 0 |
236 | + |
237 | |
238 | === removed file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh' |
239 | --- hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 2015-02-19 20:12:22 +0000 |
240 | +++ hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 1970-01-01 00:00:00 +0000 |
241 | @@ -1,30 +0,0 @@ |
242 | -#!/bin/bash |
243 | -#-------------------------------------------- |
244 | -# This file is managed by Juju |
245 | -#-------------------------------------------- |
246 | -# |
247 | -# Copyright 2009,2012 Canonical Ltd. |
248 | -# Author: Tom Haddon |
249 | - |
250 | -# These should be config options at some stage |
251 | -CURRQthrsh=0 |
252 | -MAXQthrsh=100 |
253 | - |
254 | -AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') |
255 | - |
256 | -HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v) |
257 | - |
258 | -for BACKEND in $(echo $HAPROXYSTATS| xargs -n1 | grep BACKEND | awk -F , '{print $1}') |
259 | -do |
260 | - CURRQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 3) |
261 | - MAXQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 4) |
262 | - |
263 | - if [[ $CURRQ -gt $CURRQthrsh || $MAXQ -gt $MAXQthrsh ]] ; then |
264 | - echo "CRITICAL: queue depth for $BACKEND - CURRENT:$CURRQ MAX:$MAXQ" |
265 | - exit 2 |
266 | - fi |
267 | -done |
268 | - |
269 | -echo "OK: All haproxy queue depths looking good" |
270 | -exit 0 |
271 | - |
272 | |
273 | === added file 'hooks/charmhelpers/contrib/openstack/templates/zeromq' |
274 | --- hooks/charmhelpers/contrib/openstack/templates/zeromq 1970-01-01 00:00:00 +0000 |
275 | +++ hooks/charmhelpers/contrib/openstack/templates/zeromq 2015-02-25 23:25:31 +0000 |
276 | @@ -0,0 +1,14 @@ |
277 | +{% if zmq_host -%} |
278 | +# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }}) |
279 | +rpc_backend = zmq |
280 | +rpc_zmq_host = {{ zmq_host }} |
281 | +{% if zmq_redis_address -%} |
282 | +rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_redis.MatchMakerRedis |
283 | +matchmaker_heartbeat_freq = 15 |
284 | +matchmaker_heartbeat_ttl = 30 |
285 | +[matchmaker_redis] |
286 | +host = {{ zmq_redis_address }} |
287 | +{% else -%} |
288 | +rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_ring.MatchMakerRing |
289 | +{% endif -%} |
290 | +{% endif -%} |
291 | |
292 | === added file 'hooks/charmhelpers/core/strutils.py' |
293 | --- hooks/charmhelpers/core/strutils.py 1970-01-01 00:00:00 +0000 |
294 | +++ hooks/charmhelpers/core/strutils.py 2015-02-25 23:25:31 +0000 |
295 | @@ -0,0 +1,42 @@ |
296 | +#!/usr/bin/env python |
297 | +# -*- coding: utf-8 -*- |
298 | + |
299 | +# Copyright 2014-2015 Canonical Limited. |
300 | +# |
301 | +# This file is part of charm-helpers. |
302 | +# |
303 | +# charm-helpers is free software: you can redistribute it and/or modify |
304 | +# it under the terms of the GNU Lesser General Public License version 3 as |
305 | +# published by the Free Software Foundation. |
306 | +# |
307 | +# charm-helpers is distributed in the hope that it will be useful, |
308 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
309 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
310 | +# GNU Lesser General Public License for more details. |
311 | +# |
312 | +# You should have received a copy of the GNU Lesser General Public License |
313 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
314 | + |
315 | +import six |
316 | + |
317 | + |
318 | +def bool_from_string(value): |
319 | + """Interpret string value as boolean. |
320 | + |
321 | + Returns True if value translates to True otherwise False. |
322 | + """ |
323 | + if isinstance(value, six.string_types): |
324 | + value = six.text_type(value) |
325 | + else: |
326 | + msg = "Unable to interpret non-string value '%s' as boolean" % (value) |
327 | + raise ValueError(msg) |
328 | + |
329 | + value = value.strip().lower() |
330 | + |
331 | + if value in ['y', 'yes', 'true', 't']: |
332 | + return True |
333 | + elif value in ['n', 'no', 'false', 'f']: |
334 | + return False |
335 | + |
336 | + msg = "Unable to interpret string value '%s' as boolean" % (value) |
337 | + raise ValueError(msg) |
338 | |
339 | === removed file 'hooks/charmhelpers/core/strutils.py' |
340 | --- hooks/charmhelpers/core/strutils.py 2015-02-19 20:12:22 +0000 |
341 | +++ hooks/charmhelpers/core/strutils.py 1970-01-01 00:00:00 +0000 |
342 | @@ -1,42 +0,0 @@ |
343 | -#!/usr/bin/env python |
344 | -# -*- coding: utf-8 -*- |
345 | - |
346 | -# Copyright 2014-2015 Canonical Limited. |
347 | -# |
348 | -# This file is part of charm-helpers. |
349 | -# |
350 | -# charm-helpers is free software: you can redistribute it and/or modify |
351 | -# it under the terms of the GNU Lesser General Public License version 3 as |
352 | -# published by the Free Software Foundation. |
353 | -# |
354 | -# charm-helpers is distributed in the hope that it will be useful, |
355 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
356 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
357 | -# GNU Lesser General Public License for more details. |
358 | -# |
359 | -# You should have received a copy of the GNU Lesser General Public License |
360 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
361 | - |
362 | -import six |
363 | - |
364 | - |
365 | -def bool_from_string(value): |
366 | - """Interpret string value as boolean. |
367 | - |
368 | - Returns True if value translates to True otherwise False. |
369 | - """ |
370 | - if isinstance(value, six.string_types): |
371 | - value = six.text_type(value) |
372 | - else: |
373 | - msg = "Unable to interpret non-string value '%s' as boolean" % (value) |
374 | - raise ValueError(msg) |
375 | - |
376 | - value = value.strip().lower() |
377 | - |
378 | - if value in ['y', 'yes', 'true', 't']: |
379 | - return True |
380 | - elif value in ['n', 'no', 'false', 'f']: |
381 | - return False |
382 | - |
383 | - msg = "Unable to interpret string value '%s' as boolean" % (value) |
384 | - raise ValueError(msg) |
385 | |
386 | === added file 'hooks/charmhelpers/core/unitdata.py' |
387 | --- hooks/charmhelpers/core/unitdata.py 1970-01-01 00:00:00 +0000 |
388 | +++ hooks/charmhelpers/core/unitdata.py 2015-02-25 23:25:31 +0000 |
389 | @@ -0,0 +1,477 @@ |
390 | +#!/usr/bin/env python |
391 | +# -*- coding: utf-8 -*- |
392 | +# |
393 | +# Copyright 2014-2015 Canonical Limited. |
394 | +# |
395 | +# This file is part of charm-helpers. |
396 | +# |
397 | +# charm-helpers is free software: you can redistribute it and/or modify |
398 | +# it under the terms of the GNU Lesser General Public License version 3 as |
399 | +# published by the Free Software Foundation. |
400 | +# |
401 | +# charm-helpers is distributed in the hope that it will be useful, |
402 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
403 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
404 | +# GNU Lesser General Public License for more details. |
405 | +# |
406 | +# You should have received a copy of the GNU Lesser General Public License |
407 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
408 | +# |
409 | +# |
410 | +# Authors: |
411 | +# Kapil Thangavelu <kapil.foss@gmail.com> |
412 | +# |
413 | +""" |
414 | +Intro |
415 | +----- |
416 | + |
417 | +A simple way to store state in units. This provides a key value |
418 | +storage with support for versioned, transactional operation, |
419 | +and can calculate deltas from previous values to simplify unit logic |
420 | +when processing changes. |
421 | + |
422 | + |
423 | +Hook Integration |
424 | +---------------- |
425 | + |
426 | +There are several extant frameworks for hook execution, including |
427 | + |
428 | + - charmhelpers.core.hookenv.Hooks |
429 | + - charmhelpers.core.services.ServiceManager |
430 | + |
431 | +The storage classes are framework agnostic, one simple integration is |
432 | +via the HookData contextmanager. It will record the current hook |
433 | +execution environment (including relation data, config data, etc.), |
434 | +setup a transaction and allow easy access to the changes from |
435 | +previously seen values. One consequence of the integration is the |
436 | +reservation of particular keys ('rels', 'unit', 'env', 'config', |
437 | +'charm_revisions') for their respective values. |
438 | + |
439 | +Here's a fully worked integration example using hookenv.Hooks:: |
440 | + |
441 | + from charmhelper.core import hookenv, unitdata |
442 | + |
443 | + hook_data = unitdata.HookData() |
444 | + db = unitdata.kv() |
445 | + hooks = hookenv.Hooks() |
446 | + |
447 | + @hooks.hook |
448 | + def config_changed(): |
449 | + # Print all changes to configuration from previously seen |
450 | + # values. |
451 | + for changed, (prev, cur) in hook_data.conf.items(): |
452 | + print('config changed', changed, |
453 | + 'previous value', prev, |
454 | + 'current value', cur) |
455 | + |
456 | + # Get some unit specific bookeeping |
457 | + if not db.get('pkg_key'): |
458 | + key = urllib.urlopen('https://example.com/pkg_key').read() |
459 | + db.set('pkg_key', key) |
460 | + |
461 | + # Directly access all charm config as a mapping. |
462 | + conf = db.getrange('config', True) |
463 | + |
464 | + # Directly access all relation data as a mapping |
465 | + rels = db.getrange('rels', True) |
466 | + |
467 | + if __name__ == '__main__': |
468 | + with hook_data(): |
469 | + hook.execute() |
470 | + |
471 | + |
472 | +A more basic integration is via the hook_scope context manager which simply |
473 | +manages transaction scope (and records hook name, and timestamp):: |
474 | + |
475 | + >>> from unitdata import kv |
476 | + >>> db = kv() |
477 | + >>> with db.hook_scope('install'): |
478 | + ... # do work, in transactional scope. |
479 | + ... db.set('x', 1) |
480 | + >>> db.get('x') |
481 | + 1 |
482 | + |
483 | + |
484 | +Usage |
485 | +----- |
486 | + |
487 | +Values are automatically json de/serialized to preserve basic typing |
488 | +and complex data struct capabilities (dicts, lists, ints, booleans, etc). |
489 | + |
490 | +Individual values can be manipulated via get/set:: |
491 | + |
492 | + >>> kv.set('y', True) |
493 | + >>> kv.get('y') |
494 | + True |
495 | + |
496 | + # We can set complex values (dicts, lists) as a single key. |
497 | + >>> kv.set('config', {'a': 1, 'b': True'}) |
498 | + |
499 | + # Also supports returning dictionaries as a record which |
500 | + # provides attribute access. |
501 | + >>> config = kv.get('config', record=True) |
502 | + >>> config.b |
503 | + True |
504 | + |
505 | + |
506 | +Groups of keys can be manipulated with update/getrange:: |
507 | + |
508 | + >>> kv.update({'z': 1, 'y': 2}, prefix="gui.") |
509 | + >>> kv.getrange('gui.', strip=True) |
510 | + {'z': 1, 'y': 2} |
511 | + |
512 | +When updating values, its very helpful to understand which values |
513 | +have actually changed and how have they changed. The storage |
514 | +provides a delta method to provide for this:: |
515 | + |
516 | + >>> data = {'debug': True, 'option': 2} |
517 | + >>> delta = kv.delta(data, 'config.') |
518 | + >>> delta.debug.previous |
519 | + None |
520 | + >>> delta.debug.current |
521 | + True |
522 | + >>> delta |
523 | + {'debug': (None, True), 'option': (None, 2)} |
524 | + |
525 | +Note the delta method does not persist the actual change, it needs to |
526 | +be explicitly saved via 'update' method:: |
527 | + |
528 | + >>> kv.update(data, 'config.') |
529 | + |
530 | +Values modified in the context of a hook scope retain historical values |
531 | +associated to the hookname. |
532 | + |
533 | + >>> with db.hook_scope('config-changed'): |
534 | + ... db.set('x', 42) |
535 | + >>> db.gethistory('x') |
536 | + [(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'), |
537 | + (2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')] |
538 | + |
539 | +""" |
540 | + |
541 | +import collections |
542 | +import contextlib |
543 | +import datetime |
544 | +import json |
545 | +import os |
546 | +import pprint |
547 | +import sqlite3 |
548 | +import sys |
549 | + |
550 | +__author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>' |
551 | + |
552 | + |
553 | +class Storage(object): |
554 | + """Simple key value database for local unit state within charms. |
555 | + |
556 | + Modifications are automatically committed at hook exit. That's |
557 | + currently regardless of exit code. |
558 | + |
559 | + To support dicts, lists, integer, floats, and booleans values |
560 | + are automatically json encoded/decoded. |
561 | + """ |
562 | + def __init__(self, path=None): |
563 | + self.db_path = path |
564 | + if path is None: |
565 | + self.db_path = os.path.join( |
566 | + os.environ.get('CHARM_DIR', ''), '.unit-state.db') |
567 | + self.conn = sqlite3.connect('%s' % self.db_path) |
568 | + self.cursor = self.conn.cursor() |
569 | + self.revision = None |
570 | + self._closed = False |
571 | + self._init() |
572 | + |
573 | + def close(self): |
574 | + if self._closed: |
575 | + return |
576 | + self.flush(False) |
577 | + self.cursor.close() |
578 | + self.conn.close() |
579 | + self._closed = True |
580 | + |
581 | + def _scoped_query(self, stmt, params=None): |
582 | + if params is None: |
583 | + params = [] |
584 | + return stmt, params |
585 | + |
586 | + def get(self, key, default=None, record=False): |
587 | + self.cursor.execute( |
588 | + *self._scoped_query( |
589 | + 'select data from kv where key=?', [key])) |
590 | + result = self.cursor.fetchone() |
591 | + if not result: |
592 | + return default |
593 | + if record: |
594 | + return Record(json.loads(result[0])) |
595 | + return json.loads(result[0]) |
596 | + |
597 | + def getrange(self, key_prefix, strip=False): |
598 | + stmt = "select key, data from kv where key like '%s%%'" % key_prefix |
599 | + self.cursor.execute(*self._scoped_query(stmt)) |
600 | + result = self.cursor.fetchall() |
601 | + |
602 | + if not result: |
603 | + return None |
604 | + if not strip: |
605 | + key_prefix = '' |
606 | + return dict([ |
607 | + (k[len(key_prefix):], json.loads(v)) for k, v in result]) |
608 | + |
609 | + def update(self, mapping, prefix=""): |
610 | + for k, v in mapping.items(): |
611 | + self.set("%s%s" % (prefix, k), v) |
612 | + |
613 | + def unset(self, key): |
614 | + self.cursor.execute('delete from kv where key=?', [key]) |
615 | + if self.revision and self.cursor.rowcount: |
616 | + self.cursor.execute( |
617 | + 'insert into kv_revisions values (?, ?, ?)', |
618 | + [key, self.revision, json.dumps('DELETED')]) |
619 | + |
620 | + def set(self, key, value): |
621 | + serialized = json.dumps(value) |
622 | + |
623 | + self.cursor.execute( |
624 | + 'select data from kv where key=?', [key]) |
625 | + exists = self.cursor.fetchone() |
626 | + |
627 | + # Skip mutations to the same value |
628 | + if exists: |
629 | + if exists[0] == serialized: |
630 | + return value |
631 | + |
632 | + if not exists: |
633 | + self.cursor.execute( |
634 | + 'insert into kv (key, data) values (?, ?)', |
635 | + (key, serialized)) |
636 | + else: |
637 | + self.cursor.execute(''' |
638 | + update kv |
639 | + set data = ? |
640 | + where key = ?''', [serialized, key]) |
641 | + |
642 | + # Save |
643 | + if not self.revision: |
644 | + return value |
645 | + |
646 | + self.cursor.execute( |
647 | + 'select 1 from kv_revisions where key=? and revision=?', |
648 | + [key, self.revision]) |
649 | + exists = self.cursor.fetchone() |
650 | + |
651 | + if not exists: |
652 | + self.cursor.execute( |
653 | + '''insert into kv_revisions ( |
654 | + revision, key, data) values (?, ?, ?)''', |
655 | + (self.revision, key, serialized)) |
656 | + else: |
657 | + self.cursor.execute( |
658 | + ''' |
659 | + update kv_revisions |
660 | + set data = ? |
661 | + where key = ? |
662 | + and revision = ?''', |
663 | + [serialized, key, self.revision]) |
664 | + |
665 | + return value |
666 | + |
667 | + def delta(self, mapping, prefix): |
668 | + """ |
669 | + return a delta containing values that have changed. |
670 | + """ |
671 | + previous = self.getrange(prefix, strip=True) |
672 | + if not previous: |
673 | + pk = set() |
674 | + else: |
675 | + pk = set(previous.keys()) |
676 | + ck = set(mapping.keys()) |
677 | + delta = DeltaSet() |
678 | + |
679 | + # added |
680 | + for k in ck.difference(pk): |
681 | + delta[k] = Delta(None, mapping[k]) |
682 | + |
683 | + # removed |
684 | + for k in pk.difference(ck): |
685 | + delta[k] = Delta(previous[k], None) |
686 | + |
687 | + # changed |
688 | + for k in pk.intersection(ck): |
689 | + c = mapping[k] |
690 | + p = previous[k] |
691 | + if c != p: |
692 | + delta[k] = Delta(p, c) |
693 | + |
694 | + return delta |
695 | + |
696 | + @contextlib.contextmanager |
697 | + def hook_scope(self, name=""): |
698 | + """Scope all future interactions to the current hook execution |
699 | + revision.""" |
700 | + assert not self.revision |
701 | + self.cursor.execute( |
702 | + 'insert into hooks (hook, date) values (?, ?)', |
703 | + (name or sys.argv[0], |
704 | + datetime.datetime.utcnow().isoformat())) |
705 | + self.revision = self.cursor.lastrowid |
706 | + try: |
707 | + yield self.revision |
708 | + self.revision = None |
709 | + except: |
710 | + self.flush(False) |
711 | + self.revision = None |
712 | + raise |
713 | + else: |
714 | + self.flush() |
715 | + |
716 | + def flush(self, save=True): |
717 | + if save: |
718 | + self.conn.commit() |
719 | + elif self._closed: |
720 | + return |
721 | + else: |
722 | + self.conn.rollback() |
723 | + |
724 | + def _init(self): |
725 | + self.cursor.execute(''' |
726 | + create table if not exists kv ( |
727 | + key text, |
728 | + data text, |
729 | + primary key (key) |
730 | + )''') |
731 | + self.cursor.execute(''' |
732 | + create table if not exists kv_revisions ( |
733 | + key text, |
734 | + revision integer, |
735 | + data text, |
736 | + primary key (key, revision) |
737 | + )''') |
738 | + self.cursor.execute(''' |
739 | + create table if not exists hooks ( |
740 | + version integer primary key autoincrement, |
741 | + hook text, |
742 | + date text |
743 | + )''') |
744 | + self.conn.commit() |
745 | + |
746 | + def gethistory(self, key, deserialize=False): |
747 | + self.cursor.execute( |
748 | + ''' |
749 | + select kv.revision, kv.key, kv.data, h.hook, h.date |
750 | + from kv_revisions kv, |
751 | + hooks h |
752 | + where kv.key=? |
753 | + and kv.revision = h.version |
754 | + ''', [key]) |
755 | + if deserialize is False: |
756 | + return self.cursor.fetchall() |
757 | + return map(_parse_history, self.cursor.fetchall()) |
758 | + |
759 | + def debug(self, fh=sys.stderr): |
760 | + self.cursor.execute('select * from kv') |
761 | + pprint.pprint(self.cursor.fetchall(), stream=fh) |
762 | + self.cursor.execute('select * from kv_revisions') |
763 | + pprint.pprint(self.cursor.fetchall(), stream=fh) |
764 | + |
765 | + |
766 | +def _parse_history(d): |
767 | + return (d[0], d[1], json.loads(d[2]), d[3], |
768 | + datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f")) |
769 | + |
770 | + |
771 | +class HookData(object): |
772 | + """Simple integration for existing hook exec frameworks. |
773 | + |
774 | + Records all unit information, and stores deltas for processing |
775 | + by the hook. |
776 | + |
777 | + Sample:: |
778 | + |
779 | + from charmhelper.core import hookenv, unitdata |
780 | + |
781 | + changes = unitdata.HookData() |
782 | + db = unitdata.kv() |
783 | + hooks = hookenv.Hooks() |
784 | + |
785 | + @hooks.hook |
786 | + def config_changed(): |
787 | + # View all changes to configuration |
788 | + for changed, (prev, cur) in changes.conf.items(): |
789 | + print('config changed', changed, |
790 | + 'previous value', prev, |
791 | + 'current value', cur) |
792 | + |
793 | + # Get some unit specific bookeeping |
794 | + if not db.get('pkg_key'): |
795 | + key = urllib.urlopen('https://example.com/pkg_key').read() |
796 | + db.set('pkg_key', key) |
797 | + |
798 | + if __name__ == '__main__': |
799 | + with changes(): |
800 | + hook.execute() |
801 | + |
802 | + """ |
803 | + def __init__(self): |
804 | + self.kv = kv() |
805 | + self.conf = None |
806 | + self.rels = None |
807 | + |
808 | + @contextlib.contextmanager |
809 | + def __call__(self): |
810 | + from charmhelpers.core import hookenv |
811 | + hook_name = hookenv.hook_name() |
812 | + |
813 | + with self.kv.hook_scope(hook_name): |
814 | + self._record_charm_version(hookenv.charm_dir()) |
815 | + delta_config, delta_relation = self._record_hook(hookenv) |
816 | + yield self.kv, delta_config, delta_relation |
817 | + |
818 | + def _record_charm_version(self, charm_dir): |
819 | + # Record revisions.. charm revisions are meaningless |
820 | + # to charm authors as they don't control the revision. |
821 | + # so logic dependnent on revision is not particularly |
822 | + # useful, however it is useful for debugging analysis. |
823 | + charm_rev = open( |
824 | + os.path.join(charm_dir, 'revision')).read().strip() |
825 | + charm_rev = charm_rev or '0' |
826 | + revs = self.kv.get('charm_revisions', []) |
827 | + if charm_rev not in revs: |
828 | + revs.append(charm_rev.strip() or '0') |
829 | + self.kv.set('charm_revisions', revs) |
830 | + |
831 | + def _record_hook(self, hookenv): |
832 | + data = hookenv.execution_environment() |
833 | + self.conf = conf_delta = self.kv.delta(data['conf'], 'config') |
834 | + self.rels = rels_delta = self.kv.delta(data['rels'], 'rels') |
835 | + self.kv.set('env', data['env']) |
836 | + self.kv.set('unit', data['unit']) |
837 | + self.kv.set('relid', data.get('relid')) |
838 | + return conf_delta, rels_delta |
839 | + |
840 | + |
841 | +class Record(dict): |
842 | + |
843 | + __slots__ = () |
844 | + |
845 | + def __getattr__(self, k): |
846 | + if k in self: |
847 | + return self[k] |
848 | + raise AttributeError(k) |
849 | + |
850 | + |
851 | +class DeltaSet(Record): |
852 | + |
853 | + __slots__ = () |
854 | + |
855 | + |
856 | +Delta = collections.namedtuple('Delta', ['previous', 'current']) |
857 | + |
858 | + |
859 | +_KV = None |
860 | + |
861 | + |
862 | +def kv(): |
863 | + global _KV |
864 | + if _KV is None: |
865 | + _KV = Storage() |
866 | + return _KV |
867 | |
868 | === removed file 'hooks/charmhelpers/core/unitdata.py' |
869 | --- hooks/charmhelpers/core/unitdata.py 2015-02-19 20:12:22 +0000 |
870 | +++ hooks/charmhelpers/core/unitdata.py 1970-01-01 00:00:00 +0000 |
871 | @@ -1,477 +0,0 @@ |
872 | -#!/usr/bin/env python |
873 | -# -*- coding: utf-8 -*- |
874 | -# |
875 | -# Copyright 2014-2015 Canonical Limited. |
876 | -# |
877 | -# This file is part of charm-helpers. |
878 | -# |
879 | -# charm-helpers is free software: you can redistribute it and/or modify |
880 | -# it under the terms of the GNU Lesser General Public License version 3 as |
881 | -# published by the Free Software Foundation. |
882 | -# |
883 | -# charm-helpers is distributed in the hope that it will be useful, |
884 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
885 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
886 | -# GNU Lesser General Public License for more details. |
887 | -# |
888 | -# You should have received a copy of the GNU Lesser General Public License |
889 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
890 | -# |
891 | -# |
892 | -# Authors: |
893 | -# Kapil Thangavelu <kapil.foss@gmail.com> |
894 | -# |
895 | -""" |
896 | -Intro |
897 | ------ |
898 | - |
899 | -A simple way to store state in units. This provides a key value |
900 | -storage with support for versioned, transactional operation, |
901 | -and can calculate deltas from previous values to simplify unit logic |
902 | -when processing changes. |
903 | - |
904 | - |
905 | -Hook Integration |
906 | ----------------- |
907 | - |
908 | -There are several extant frameworks for hook execution, including |
909 | - |
910 | - - charmhelpers.core.hookenv.Hooks |
911 | - - charmhelpers.core.services.ServiceManager |
912 | - |
913 | -The storage classes are framework agnostic, one simple integration is |
914 | -via the HookData contextmanager. It will record the current hook |
915 | -execution environment (including relation data, config data, etc.), |
916 | -setup a transaction and allow easy access to the changes from |
917 | -previously seen values. One consequence of the integration is the |
918 | -reservation of particular keys ('rels', 'unit', 'env', 'config', |
919 | -'charm_revisions') for their respective values. |
920 | - |
921 | -Here's a fully worked integration example using hookenv.Hooks:: |
922 | - |
923 | - from charmhelper.core import hookenv, unitdata |
924 | - |
925 | - hook_data = unitdata.HookData() |
926 | - db = unitdata.kv() |
927 | - hooks = hookenv.Hooks() |
928 | - |
929 | - @hooks.hook |
930 | - def config_changed(): |
931 | - # Print all changes to configuration from previously seen |
932 | - # values. |
933 | - for changed, (prev, cur) in hook_data.conf.items(): |
934 | - print('config changed', changed, |
935 | - 'previous value', prev, |
936 | - 'current value', cur) |
937 | - |
938 | - # Get some unit specific bookeeping |
939 | - if not db.get('pkg_key'): |
940 | - key = urllib.urlopen('https://example.com/pkg_key').read() |
941 | - db.set('pkg_key', key) |
942 | - |
943 | - # Directly access all charm config as a mapping. |
944 | - conf = db.getrange('config', True) |
945 | - |
946 | - # Directly access all relation data as a mapping |
947 | - rels = db.getrange('rels', True) |
948 | - |
949 | - if __name__ == '__main__': |
950 | - with hook_data(): |
951 | - hook.execute() |
952 | - |
953 | - |
954 | -A more basic integration is via the hook_scope context manager which simply |
955 | -manages transaction scope (and records hook name, and timestamp):: |
956 | - |
957 | - >>> from unitdata import kv |
958 | - >>> db = kv() |
959 | - >>> with db.hook_scope('install'): |
960 | - ... # do work, in transactional scope. |
961 | - ... db.set('x', 1) |
962 | - >>> db.get('x') |
963 | - 1 |
964 | - |
965 | - |
966 | -Usage |
967 | ------ |
968 | - |
969 | -Values are automatically json de/serialized to preserve basic typing |
970 | -and complex data struct capabilities (dicts, lists, ints, booleans, etc). |
971 | - |
972 | -Individual values can be manipulated via get/set:: |
973 | - |
974 | - >>> kv.set('y', True) |
975 | - >>> kv.get('y') |
976 | - True |
977 | - |
978 | - # We can set complex values (dicts, lists) as a single key. |
979 | - >>> kv.set('config', {'a': 1, 'b': True'}) |
980 | - |
981 | - # Also supports returning dictionaries as a record which |
982 | - # provides attribute access. |
983 | - >>> config = kv.get('config', record=True) |
984 | - >>> config.b |
985 | - True |
986 | - |
987 | - |
988 | -Groups of keys can be manipulated with update/getrange:: |
989 | - |
990 | - >>> kv.update({'z': 1, 'y': 2}, prefix="gui.") |
991 | - >>> kv.getrange('gui.', strip=True) |
992 | - {'z': 1, 'y': 2} |
993 | - |
994 | -When updating values, its very helpful to understand which values |
995 | -have actually changed and how have they changed. The storage |
996 | -provides a delta method to provide for this:: |
997 | - |
998 | - >>> data = {'debug': True, 'option': 2} |
999 | - >>> delta = kv.delta(data, 'config.') |
1000 | - >>> delta.debug.previous |
1001 | - None |
1002 | - >>> delta.debug.current |
1003 | - True |
1004 | - >>> delta |
1005 | - {'debug': (None, True), 'option': (None, 2)} |
1006 | - |
1007 | -Note the delta method does not persist the actual change, it needs to |
1008 | -be explicitly saved via 'update' method:: |
1009 | - |
1010 | - >>> kv.update(data, 'config.') |
1011 | - |
1012 | -Values modified in the context of a hook scope retain historical values |
1013 | -associated to the hookname. |
1014 | - |
1015 | - >>> with db.hook_scope('config-changed'): |
1016 | - ... db.set('x', 42) |
1017 | - >>> db.gethistory('x') |
1018 | - [(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'), |
1019 | - (2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')] |
1020 | - |
1021 | -""" |
1022 | - |
1023 | -import collections |
1024 | -import contextlib |
1025 | -import datetime |
1026 | -import json |
1027 | -import os |
1028 | -import pprint |
1029 | -import sqlite3 |
1030 | -import sys |
1031 | - |
1032 | -__author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>' |
1033 | - |
1034 | - |
1035 | -class Storage(object): |
1036 | - """Simple key value database for local unit state within charms. |
1037 | - |
1038 | - Modifications are automatically committed at hook exit. That's |
1039 | - currently regardless of exit code. |
1040 | - |
1041 | - To support dicts, lists, integer, floats, and booleans values |
1042 | - are automatically json encoded/decoded. |
1043 | - """ |
1044 | - def __init__(self, path=None): |
1045 | - self.db_path = path |
1046 | - if path is None: |
1047 | - self.db_path = os.path.join( |
1048 | - os.environ.get('CHARM_DIR', ''), '.unit-state.db') |
1049 | - self.conn = sqlite3.connect('%s' % self.db_path) |
1050 | - self.cursor = self.conn.cursor() |
1051 | - self.revision = None |
1052 | - self._closed = False |
1053 | - self._init() |
1054 | - |
1055 | - def close(self): |
1056 | - if self._closed: |
1057 | - return |
1058 | - self.flush(False) |
1059 | - self.cursor.close() |
1060 | - self.conn.close() |
1061 | - self._closed = True |
1062 | - |
1063 | - def _scoped_query(self, stmt, params=None): |
1064 | - if params is None: |
1065 | - params = [] |
1066 | - return stmt, params |
1067 | - |
1068 | - def get(self, key, default=None, record=False): |
1069 | - self.cursor.execute( |
1070 | - *self._scoped_query( |
1071 | - 'select data from kv where key=?', [key])) |
1072 | - result = self.cursor.fetchone() |
1073 | - if not result: |
1074 | - return default |
1075 | - if record: |
1076 | - return Record(json.loads(result[0])) |
1077 | - return json.loads(result[0]) |
1078 | - |
1079 | - def getrange(self, key_prefix, strip=False): |
1080 | - stmt = "select key, data from kv where key like '%s%%'" % key_prefix |
1081 | - self.cursor.execute(*self._scoped_query(stmt)) |
1082 | - result = self.cursor.fetchall() |
1083 | - |
1084 | - if not result: |
1085 | - return None |
1086 | - if not strip: |
1087 | - key_prefix = '' |
1088 | - return dict([ |
1089 | - (k[len(key_prefix):], json.loads(v)) for k, v in result]) |
1090 | - |
1091 | - def update(self, mapping, prefix=""): |
1092 | - for k, v in mapping.items(): |
1093 | - self.set("%s%s" % (prefix, k), v) |
1094 | - |
1095 | - def unset(self, key): |
1096 | - self.cursor.execute('delete from kv where key=?', [key]) |
1097 | - if self.revision and self.cursor.rowcount: |
1098 | - self.cursor.execute( |
1099 | - 'insert into kv_revisions values (?, ?, ?)', |
1100 | - [key, self.revision, json.dumps('DELETED')]) |
1101 | - |
1102 | - def set(self, key, value): |
1103 | - serialized = json.dumps(value) |
1104 | - |
1105 | - self.cursor.execute( |
1106 | - 'select data from kv where key=?', [key]) |
1107 | - exists = self.cursor.fetchone() |
1108 | - |
1109 | - # Skip mutations to the same value |
1110 | - if exists: |
1111 | - if exists[0] == serialized: |
1112 | - return value |
1113 | - |
1114 | - if not exists: |
1115 | - self.cursor.execute( |
1116 | - 'insert into kv (key, data) values (?, ?)', |
1117 | - (key, serialized)) |
1118 | - else: |
1119 | - self.cursor.execute(''' |
1120 | - update kv |
1121 | - set data = ? |
1122 | - where key = ?''', [serialized, key]) |
1123 | - |
1124 | - # Save |
1125 | - if not self.revision: |
1126 | - return value |
1127 | - |
1128 | - self.cursor.execute( |
1129 | - 'select 1 from kv_revisions where key=? and revision=?', |
1130 | - [key, self.revision]) |
1131 | - exists = self.cursor.fetchone() |
1132 | - |
1133 | - if not exists: |
1134 | - self.cursor.execute( |
1135 | - '''insert into kv_revisions ( |
1136 | - revision, key, data) values (?, ?, ?)''', |
1137 | - (self.revision, key, serialized)) |
1138 | - else: |
1139 | - self.cursor.execute( |
1140 | - ''' |
1141 | - update kv_revisions |
1142 | - set data = ? |
1143 | - where key = ? |
1144 | - and revision = ?''', |
1145 | - [serialized, key, self.revision]) |
1146 | - |
1147 | - return value |
1148 | - |
1149 | - def delta(self, mapping, prefix): |
1150 | - """ |
1151 | - return a delta containing values that have changed. |
1152 | - """ |
1153 | - previous = self.getrange(prefix, strip=True) |
1154 | - if not previous: |
1155 | - pk = set() |
1156 | - else: |
1157 | - pk = set(previous.keys()) |
1158 | - ck = set(mapping.keys()) |
1159 | - delta = DeltaSet() |
1160 | - |
1161 | - # added |
1162 | - for k in ck.difference(pk): |
1163 | - delta[k] = Delta(None, mapping[k]) |
1164 | - |
1165 | - # removed |
1166 | - for k in pk.difference(ck): |
1167 | - delta[k] = Delta(previous[k], None) |
1168 | - |
1169 | - # changed |
1170 | - for k in pk.intersection(ck): |
1171 | - c = mapping[k] |
1172 | - p = previous[k] |
1173 | - if c != p: |
1174 | - delta[k] = Delta(p, c) |
1175 | - |
1176 | - return delta |
1177 | - |
1178 | - @contextlib.contextmanager |
1179 | - def hook_scope(self, name=""): |
1180 | - """Scope all future interactions to the current hook execution |
1181 | - revision.""" |
1182 | - assert not self.revision |
1183 | - self.cursor.execute( |
1184 | - 'insert into hooks (hook, date) values (?, ?)', |
1185 | - (name or sys.argv[0], |
1186 | - datetime.datetime.utcnow().isoformat())) |
1187 | - self.revision = self.cursor.lastrowid |
1188 | - try: |
1189 | - yield self.revision |
1190 | - self.revision = None |
1191 | - except: |
1192 | - self.flush(False) |
1193 | - self.revision = None |
1194 | - raise |
1195 | - else: |
1196 | - self.flush() |
1197 | - |
1198 | - def flush(self, save=True): |
1199 | - if save: |
1200 | - self.conn.commit() |
1201 | - elif self._closed: |
1202 | - return |
1203 | - else: |
1204 | - self.conn.rollback() |
1205 | - |
1206 | - def _init(self): |
1207 | - self.cursor.execute(''' |
1208 | - create table if not exists kv ( |
1209 | - key text, |
1210 | - data text, |
1211 | - primary key (key) |
1212 | - )''') |
1213 | - self.cursor.execute(''' |
1214 | - create table if not exists kv_revisions ( |
1215 | - key text, |
1216 | - revision integer, |
1217 | - data text, |
1218 | - primary key (key, revision) |
1219 | - )''') |
1220 | - self.cursor.execute(''' |
1221 | - create table if not exists hooks ( |
1222 | - version integer primary key autoincrement, |
1223 | - hook text, |
1224 | - date text |
1225 | - )''') |
1226 | - self.conn.commit() |
1227 | - |
1228 | - def gethistory(self, key, deserialize=False): |
1229 | - self.cursor.execute( |
1230 | - ''' |
1231 | - select kv.revision, kv.key, kv.data, h.hook, h.date |
1232 | - from kv_revisions kv, |
1233 | - hooks h |
1234 | - where kv.key=? |
1235 | - and kv.revision = h.version |
1236 | - ''', [key]) |
1237 | - if deserialize is False: |
1238 | - return self.cursor.fetchall() |
1239 | - return map(_parse_history, self.cursor.fetchall()) |
1240 | - |
1241 | - def debug(self, fh=sys.stderr): |
1242 | - self.cursor.execute('select * from kv') |
1243 | - pprint.pprint(self.cursor.fetchall(), stream=fh) |
1244 | - self.cursor.execute('select * from kv_revisions') |
1245 | - pprint.pprint(self.cursor.fetchall(), stream=fh) |
1246 | - |
1247 | - |
1248 | -def _parse_history(d): |
1249 | - return (d[0], d[1], json.loads(d[2]), d[3], |
1250 | - datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f")) |
1251 | - |
1252 | - |
1253 | -class HookData(object): |
1254 | - """Simple integration for existing hook exec frameworks. |
1255 | - |
1256 | - Records all unit information, and stores deltas for processing |
1257 | - by the hook. |
1258 | - |
1259 | - Sample:: |
1260 | - |
1261 | - from charmhelper.core import hookenv, unitdata |
1262 | - |
1263 | - changes = unitdata.HookData() |
1264 | - db = unitdata.kv() |
1265 | - hooks = hookenv.Hooks() |
1266 | - |
1267 | - @hooks.hook |
1268 | - def config_changed(): |
1269 | - # View all changes to configuration |
1270 | - for changed, (prev, cur) in changes.conf.items(): |
1271 | - print('config changed', changed, |
1272 | - 'previous value', prev, |
1273 | - 'current value', cur) |
1274 | - |
1275 | - # Get some unit specific bookeeping |
1276 | - if not db.get('pkg_key'): |
1277 | - key = urllib.urlopen('https://example.com/pkg_key').read() |
1278 | - db.set('pkg_key', key) |
1279 | - |
1280 | - if __name__ == '__main__': |
1281 | - with changes(): |
1282 | - hook.execute() |
1283 | - |
1284 | - """ |
1285 | - def __init__(self): |
1286 | - self.kv = kv() |
1287 | - self.conf = None |
1288 | - self.rels = None |
1289 | - |
1290 | - @contextlib.contextmanager |
1291 | - def __call__(self): |
1292 | - from charmhelpers.core import hookenv |
1293 | - hook_name = hookenv.hook_name() |
1294 | - |
1295 | - with self.kv.hook_scope(hook_name): |
1296 | - self._record_charm_version(hookenv.charm_dir()) |
1297 | - delta_config, delta_relation = self._record_hook(hookenv) |
1298 | - yield self.kv, delta_config, delta_relation |
1299 | - |
1300 | - def _record_charm_version(self, charm_dir): |
1301 | - # Record revisions.. charm revisions are meaningless |
1302 | - # to charm authors as they don't control the revision. |
1303 | - # so logic dependnent on revision is not particularly |
1304 | - # useful, however it is useful for debugging analysis. |
1305 | - charm_rev = open( |
1306 | - os.path.join(charm_dir, 'revision')).read().strip() |
1307 | - charm_rev = charm_rev or '0' |
1308 | - revs = self.kv.get('charm_revisions', []) |
1309 | - if charm_rev not in revs: |
1310 | - revs.append(charm_rev.strip() or '0') |
1311 | - self.kv.set('charm_revisions', revs) |
1312 | - |
1313 | - def _record_hook(self, hookenv): |
1314 | - data = hookenv.execution_environment() |
1315 | - self.conf = conf_delta = self.kv.delta(data['conf'], 'config') |
1316 | - self.rels = rels_delta = self.kv.delta(data['rels'], 'rels') |
1317 | - self.kv.set('env', data['env']) |
1318 | - self.kv.set('unit', data['unit']) |
1319 | - self.kv.set('relid', data.get('relid')) |
1320 | - return conf_delta, rels_delta |
1321 | - |
1322 | - |
1323 | -class Record(dict): |
1324 | - |
1325 | - __slots__ = () |
1326 | - |
1327 | - def __getattr__(self, k): |
1328 | - if k in self: |
1329 | - return self[k] |
1330 | - raise AttributeError(k) |
1331 | - |
1332 | - |
1333 | -class DeltaSet(Record): |
1334 | - |
1335 | - __slots__ = () |
1336 | - |
1337 | - |
1338 | -Delta = collections.namedtuple('Delta', ['previous', 'current']) |
1339 | - |
1340 | - |
1341 | -_KV = None |
1342 | - |
1343 | - |
1344 | -def kv(): |
1345 | - global _KV |
1346 | - if _KV is None: |
1347 | - _KV = Storage() |
1348 | - return _KV |
1349 | |
1350 | === modified file 'hooks/swift_hooks.py' |
1351 | --- hooks/swift_hooks.py 2015-01-12 12:08:10 +0000 |
1352 | +++ hooks/swift_hooks.py 2015-02-25 23:25:31 +0000 |
1353 | @@ -500,7 +500,9 @@ |
1354 | hostname = nrpe.get_nagios_hostname() |
1355 | current_unit = nrpe.get_nagios_unit_name() |
1356 | nrpe_setup = nrpe.NRPE(hostname=hostname) |
1357 | + nrpe.copy_nrpe_checks() |
1358 | nrpe.add_init_service_checks(nrpe_setup, services(), current_unit) |
1359 | + nrpe.add_haproxy_checks(nrpe_setup, current_unit) |
1360 | nrpe_setup.write() |
1361 | |
1362 | |
1363 | |
1364 | === modified file 'tests/basic_deployment.py' |
1365 | --- tests/basic_deployment.py 2015-01-05 13:43:32 +0000 |
1366 | +++ tests/basic_deployment.py 2015-02-25 23:25:31 +0000 |
1367 | @@ -20,7 +20,7 @@ |
1368 | class SwiftProxyBasicDeployment(OpenStackAmuletDeployment): |
1369 | """Amulet tests on a basic swift-proxy deployment.""" |
1370 | |
1371 | - def __init__(self, series, openstack=None, source=None, stable=False): |
1372 | + def __init__(self, series, openstack=None, source=None, stable=True): |
1373 | """Deploy the entire test environment.""" |
1374 | super(SwiftProxyBasicDeployment, self).__init__(series, openstack, |
1375 | source, stable) |
charm_lint_check #2216 swift-proxy-next for brad-marshall mp250700
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/2216/