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