Merge lp:~hopem/charms/trusty/cinder/lp1499643 into lp:~openstack-charmers-archive/charms/trusty/cinder/next
- Trusty Tahr (14.04)
- lp1499643
- Merge into next
Proposed by
Edward Hope-Morley
Status: | Merged |
---|---|
Merged at revision: | 128 |
Proposed branch: | lp:~hopem/charms/trusty/cinder/lp1499643 |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/cinder/next |
Diff against target: |
810 lines (+407/-53) 11 files modified
hooks/charmhelpers/contrib/network/ip.py (+5/-3) hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+9/-10) hooks/charmhelpers/contrib/openstack/context.py (+52/-7) hooks/charmhelpers/contrib/openstack/templating.py (+30/-2) hooks/charmhelpers/contrib/openstack/utils.py (+181/-2) hooks/charmhelpers/core/hookenv.py (+32/-0) hooks/charmhelpers/core/hugepage.py (+8/-1) hooks/charmhelpers/core/strutils.py (+30/-0) tests/charmhelpers/contrib/amulet/deployment.py (+4/-2) tests/charmhelpers/contrib/amulet/utils.py (+47/-16) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+9/-10) |
To merge this branch: | bzr merge lp:~hopem/charms/trusty/cinder/lp1499643 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+272532@code.launchpad.net |
Commit message
Description of the change
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 #10058 cinder-next for hopem mp272532
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6841 cinder-next for hopem mp272532
AMULET OK: passed
Build: http://
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'hooks/charmhelpers/contrib/network/ip.py' |
2 | --- hooks/charmhelpers/contrib/network/ip.py 2015-09-03 09:43:34 +0000 |
3 | +++ hooks/charmhelpers/contrib/network/ip.py 2015-09-27 18:46:57 +0000 |
4 | @@ -23,7 +23,7 @@ |
5 | from functools import partial |
6 | |
7 | from charmhelpers.core.hookenv import unit_get |
8 | -from charmhelpers.fetch import apt_install |
9 | +from charmhelpers.fetch import apt_install, apt_update |
10 | from charmhelpers.core.hookenv import ( |
11 | log, |
12 | WARNING, |
13 | @@ -32,13 +32,15 @@ |
14 | try: |
15 | import netifaces |
16 | except ImportError: |
17 | - apt_install('python-netifaces') |
18 | + apt_update(fatal=True) |
19 | + apt_install('python-netifaces', fatal=True) |
20 | import netifaces |
21 | |
22 | try: |
23 | import netaddr |
24 | except ImportError: |
25 | - apt_install('python-netaddr') |
26 | + apt_update(fatal=True) |
27 | + apt_install('python-netaddr', fatal=True) |
28 | import netaddr |
29 | |
30 | |
31 | |
32 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' |
33 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-14 20:19:57 +0000 |
34 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-27 18:46:57 +0000 |
35 | @@ -58,19 +58,17 @@ |
36 | else: |
37 | base_series = self.current_next |
38 | |
39 | - if self.stable: |
40 | - for svc in other_services: |
41 | - if svc['name'] in force_series_current: |
42 | - base_series = self.current_next |
43 | - |
44 | + for svc in other_services: |
45 | + if svc['name'] in force_series_current: |
46 | + base_series = self.current_next |
47 | + # If a location has been explicitly set, use it |
48 | + if svc.get('location'): |
49 | + continue |
50 | + if self.stable: |
51 | temp = 'lp:charms/{}/{}' |
52 | svc['location'] = temp.format(base_series, |
53 | svc['name']) |
54 | - else: |
55 | - for svc in other_services: |
56 | - if svc['name'] in force_series_current: |
57 | - base_series = self.current_next |
58 | - |
59 | + else: |
60 | if svc['name'] in base_charms: |
61 | temp = 'lp:charms/{}/{}' |
62 | svc['location'] = temp.format(base_series, |
63 | @@ -79,6 +77,7 @@ |
64 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
65 | svc['location'] = temp.format(self.current_next, |
66 | svc['name']) |
67 | + |
68 | return other_services |
69 | |
70 | def _add_services(self, this_service, other_services): |
71 | |
72 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' |
73 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-09-12 06:32:34 +0000 |
74 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-09-27 18:46:57 +0000 |
75 | @@ -194,10 +194,50 @@ |
76 | class OSContextGenerator(object): |
77 | """Base class for all context generators.""" |
78 | interfaces = [] |
79 | + related = False |
80 | + complete = False |
81 | + missing_data = [] |
82 | |
83 | def __call__(self): |
84 | raise NotImplementedError |
85 | |
86 | + def context_complete(self, ctxt): |
87 | + """Check for missing data for the required context data. |
88 | + Set self.missing_data if it exists and return False. |
89 | + Set self.complete if no missing data and return True. |
90 | + """ |
91 | + # Fresh start |
92 | + self.complete = False |
93 | + self.missing_data = [] |
94 | + for k, v in six.iteritems(ctxt): |
95 | + if v is None or v == '': |
96 | + if k not in self.missing_data: |
97 | + self.missing_data.append(k) |
98 | + |
99 | + if self.missing_data: |
100 | + self.complete = False |
101 | + log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO) |
102 | + else: |
103 | + self.complete = True |
104 | + return self.complete |
105 | + |
106 | + def get_related(self): |
107 | + """Check if any of the context interfaces have relation ids. |
108 | + Set self.related and return True if one of the interfaces |
109 | + has relation ids. |
110 | + """ |
111 | + # Fresh start |
112 | + self.related = False |
113 | + try: |
114 | + for interface in self.interfaces: |
115 | + if relation_ids(interface): |
116 | + self.related = True |
117 | + return self.related |
118 | + except AttributeError as e: |
119 | + log("{} {}" |
120 | + "".format(self, e), 'INFO') |
121 | + return self.related |
122 | + |
123 | |
124 | class SharedDBContext(OSContextGenerator): |
125 | interfaces = ['shared-db'] |
126 | @@ -213,6 +253,7 @@ |
127 | self.database = database |
128 | self.user = user |
129 | self.ssl_dir = ssl_dir |
130 | + self.rel_name = self.interfaces[0] |
131 | |
132 | def __call__(self): |
133 | self.database = self.database or config('database') |
134 | @@ -246,6 +287,7 @@ |
135 | password_setting = self.relation_prefix + '_password' |
136 | |
137 | for rid in relation_ids(self.interfaces[0]): |
138 | + self.related = True |
139 | for unit in related_units(rid): |
140 | rdata = relation_get(rid=rid, unit=unit) |
141 | host = rdata.get('db_host') |
142 | @@ -257,7 +299,7 @@ |
143 | 'database_password': rdata.get(password_setting), |
144 | 'database_type': 'mysql' |
145 | } |
146 | - if context_complete(ctxt): |
147 | + if self.context_complete(ctxt): |
148 | db_ssl(rdata, ctxt, self.ssl_dir) |
149 | return ctxt |
150 | return {} |
151 | @@ -278,6 +320,7 @@ |
152 | |
153 | ctxt = {} |
154 | for rid in relation_ids(self.interfaces[0]): |
155 | + self.related = True |
156 | for unit in related_units(rid): |
157 | rel_host = relation_get('host', rid=rid, unit=unit) |
158 | rel_user = relation_get('user', rid=rid, unit=unit) |
159 | @@ -287,7 +330,7 @@ |
160 | 'database_user': rel_user, |
161 | 'database_password': rel_passwd, |
162 | 'database_type': 'postgresql'} |
163 | - if context_complete(ctxt): |
164 | + if self.context_complete(ctxt): |
165 | return ctxt |
166 | |
167 | return {} |
168 | @@ -348,6 +391,7 @@ |
169 | ctxt['signing_dir'] = cachedir |
170 | |
171 | for rid in relation_ids(self.rel_name): |
172 | + self.related = True |
173 | for unit in related_units(rid): |
174 | rdata = relation_get(rid=rid, unit=unit) |
175 | serv_host = rdata.get('service_host') |
176 | @@ -366,7 +410,7 @@ |
177 | 'service_protocol': svc_protocol, |
178 | 'auth_protocol': auth_protocol}) |
179 | |
180 | - if context_complete(ctxt): |
181 | + if self.context_complete(ctxt): |
182 | # NOTE(jamespage) this is required for >= icehouse |
183 | # so a missing value just indicates keystone needs |
184 | # upgrading |
185 | @@ -405,6 +449,7 @@ |
186 | ctxt = {} |
187 | for rid in relation_ids(self.rel_name): |
188 | ha_vip_only = False |
189 | + self.related = True |
190 | for unit in related_units(rid): |
191 | if relation_get('clustered', rid=rid, unit=unit): |
192 | ctxt['clustered'] = True |
193 | @@ -437,7 +482,7 @@ |
194 | ha_vip_only = relation_get('ha-vip-only', |
195 | rid=rid, unit=unit) is not None |
196 | |
197 | - if context_complete(ctxt): |
198 | + if self.context_complete(ctxt): |
199 | if 'rabbit_ssl_ca' in ctxt: |
200 | if not self.ssl_dir: |
201 | log("Charm not setup for ssl support but ssl ca " |
202 | @@ -469,7 +514,7 @@ |
203 | ctxt['oslo_messaging_flags'] = config_flags_parser( |
204 | oslo_messaging_flags) |
205 | |
206 | - if not context_complete(ctxt): |
207 | + if not self.complete: |
208 | return {} |
209 | |
210 | return ctxt |
211 | @@ -507,7 +552,7 @@ |
212 | if not os.path.isdir('/etc/ceph'): |
213 | os.mkdir('/etc/ceph') |
214 | |
215 | - if not context_complete(ctxt): |
216 | + if not self.context_complete(ctxt): |
217 | return {} |
218 | |
219 | ensure_packages(['ceph-common']) |
220 | @@ -1366,6 +1411,6 @@ |
221 | 'auth_protocol': |
222 | rdata.get('auth_protocol') or 'http', |
223 | } |
224 | - if context_complete(ctxt): |
225 | + if self.context_complete(ctxt): |
226 | return ctxt |
227 | return {} |
228 | |
229 | === modified file 'hooks/charmhelpers/contrib/openstack/templating.py' |
230 | --- hooks/charmhelpers/contrib/openstack/templating.py 2015-07-29 10:49:28 +0000 |
231 | +++ hooks/charmhelpers/contrib/openstack/templating.py 2015-09-27 18:46:57 +0000 |
232 | @@ -18,7 +18,7 @@ |
233 | |
234 | import six |
235 | |
236 | -from charmhelpers.fetch import apt_install |
237 | +from charmhelpers.fetch import apt_install, apt_update |
238 | from charmhelpers.core.hookenv import ( |
239 | log, |
240 | ERROR, |
241 | @@ -29,6 +29,7 @@ |
242 | try: |
243 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
244 | except ImportError: |
245 | + apt_update(fatal=True) |
246 | apt_install('python-jinja2', fatal=True) |
247 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
248 | |
249 | @@ -112,7 +113,7 @@ |
250 | |
251 | def complete_contexts(self): |
252 | ''' |
253 | - Return a list of interfaces that have atisfied contexts. |
254 | + Return a list of interfaces that have satisfied contexts. |
255 | ''' |
256 | if self._complete_contexts: |
257 | return self._complete_contexts |
258 | @@ -293,3 +294,30 @@ |
259 | [interfaces.extend(i.complete_contexts()) |
260 | for i in six.itervalues(self.templates)] |
261 | return interfaces |
262 | + |
263 | + def get_incomplete_context_data(self, interfaces): |
264 | + ''' |
265 | + Return dictionary of relation status of interfaces and any missing |
266 | + required context data. Example: |
267 | + {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True}, |
268 | + 'zeromq-configuration': {'related': False}} |
269 | + ''' |
270 | + incomplete_context_data = {} |
271 | + |
272 | + for i in six.itervalues(self.templates): |
273 | + for context in i.contexts: |
274 | + for interface in interfaces: |
275 | + related = False |
276 | + if interface in context.interfaces: |
277 | + related = context.get_related() |
278 | + missing_data = context.missing_data |
279 | + if missing_data: |
280 | + incomplete_context_data[interface] = {'missing_data': missing_data} |
281 | + if related: |
282 | + if incomplete_context_data.get(interface): |
283 | + incomplete_context_data[interface].update({'related': True}) |
284 | + else: |
285 | + incomplete_context_data[interface] = {'related': True} |
286 | + else: |
287 | + incomplete_context_data[interface] = {'related': False} |
288 | + return incomplete_context_data |
289 | |
290 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' |
291 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-09-14 20:19:57 +0000 |
292 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2015-09-27 18:46:57 +0000 |
293 | @@ -42,7 +42,9 @@ |
294 | charm_dir, |
295 | INFO, |
296 | relation_ids, |
297 | - relation_set |
298 | + relation_set, |
299 | + status_set, |
300 | + hook_name |
301 | ) |
302 | |
303 | from charmhelpers.contrib.storage.linux.lvm import ( |
304 | @@ -52,7 +54,8 @@ |
305 | ) |
306 | |
307 | from charmhelpers.contrib.network.ip import ( |
308 | - get_ipv6_addr |
309 | + get_ipv6_addr, |
310 | + is_ipv6, |
311 | ) |
312 | |
313 | from charmhelpers.contrib.python.packages import ( |
314 | @@ -517,6 +520,12 @@ |
315 | relation_prefix=None): |
316 | hosts = get_ipv6_addr(dynamic_only=False) |
317 | |
318 | + if config('vip'): |
319 | + vips = config('vip').split() |
320 | + for vip in vips: |
321 | + if vip and is_ipv6(vip): |
322 | + hosts.append(vip) |
323 | + |
324 | kwargs = {'database': database, |
325 | 'username': database_user, |
326 | 'hostname': json.dumps(hosts)} |
327 | @@ -754,6 +763,176 @@ |
328 | return None |
329 | |
330 | |
331 | +def os_workload_status(configs, required_interfaces, charm_func=None): |
332 | + """ |
333 | + Decorator to set workload status based on complete contexts |
334 | + """ |
335 | + def wrap(f): |
336 | + @wraps(f) |
337 | + def wrapped_f(*args, **kwargs): |
338 | + # Run the original function first |
339 | + f(*args, **kwargs) |
340 | + # Set workload status now that contexts have been |
341 | + # acted on |
342 | + set_os_workload_status(configs, required_interfaces, charm_func) |
343 | + return wrapped_f |
344 | + return wrap |
345 | + |
346 | + |
347 | +def set_os_workload_status(configs, required_interfaces, charm_func=None): |
348 | + """ |
349 | + Set workload status based on complete contexts. |
350 | + status-set missing or incomplete contexts |
351 | + and juju-log details of missing required data. |
352 | + charm_func is a charm specific function to run checking |
353 | + for charm specific requirements such as a VIP setting. |
354 | + """ |
355 | + incomplete_rel_data = incomplete_relation_data(configs, required_interfaces) |
356 | + state = 'active' |
357 | + missing_relations = [] |
358 | + incomplete_relations = [] |
359 | + message = None |
360 | + charm_state = None |
361 | + charm_message = None |
362 | + |
363 | + for generic_interface in incomplete_rel_data.keys(): |
364 | + related_interface = None |
365 | + missing_data = {} |
366 | + # Related or not? |
367 | + for interface in incomplete_rel_data[generic_interface]: |
368 | + if incomplete_rel_data[generic_interface][interface].get('related'): |
369 | + related_interface = interface |
370 | + missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data') |
371 | + # No relation ID for the generic_interface |
372 | + if not related_interface: |
373 | + juju_log("{} relation is missing and must be related for " |
374 | + "functionality. ".format(generic_interface), 'WARN') |
375 | + state = 'blocked' |
376 | + if generic_interface not in missing_relations: |
377 | + missing_relations.append(generic_interface) |
378 | + else: |
379 | + # Relation ID exists but no related unit |
380 | + if not missing_data: |
381 | + # Edge case relation ID exists but departing |
382 | + if ('departed' in hook_name() or 'broken' in hook_name()) \ |
383 | + and related_interface in hook_name(): |
384 | + state = 'blocked' |
385 | + if generic_interface not in missing_relations: |
386 | + missing_relations.append(generic_interface) |
387 | + juju_log("{} relation's interface, {}, " |
388 | + "relationship is departed or broken " |
389 | + "and is required for functionality." |
390 | + "".format(generic_interface, related_interface), "WARN") |
391 | + # Normal case relation ID exists but no related unit |
392 | + # (joining) |
393 | + else: |
394 | + juju_log("{} relations's interface, {}, is related but has " |
395 | + "no units in the relation." |
396 | + "".format(generic_interface, related_interface), "INFO") |
397 | + # Related unit exists and data missing on the relation |
398 | + else: |
399 | + juju_log("{} relation's interface, {}, is related awaiting " |
400 | + "the following data from the relationship: {}. " |
401 | + "".format(generic_interface, related_interface, |
402 | + ", ".join(missing_data)), "INFO") |
403 | + if state != 'blocked': |
404 | + state = 'waiting' |
405 | + if generic_interface not in incomplete_relations \ |
406 | + and generic_interface not in missing_relations: |
407 | + incomplete_relations.append(generic_interface) |
408 | + |
409 | + if missing_relations: |
410 | + message = "Missing relations: {}".format(", ".join(missing_relations)) |
411 | + if incomplete_relations: |
412 | + message += "; incomplete relations: {}" \ |
413 | + "".format(", ".join(incomplete_relations)) |
414 | + state = 'blocked' |
415 | + elif incomplete_relations: |
416 | + message = "Incomplete relations: {}" \ |
417 | + "".format(", ".join(incomplete_relations)) |
418 | + state = 'waiting' |
419 | + |
420 | + # Run charm specific checks |
421 | + if charm_func: |
422 | + charm_state, charm_message = charm_func(configs) |
423 | + if charm_state != 'active' and charm_state != 'unknown': |
424 | + state = workload_state_compare(state, charm_state) |
425 | + if message: |
426 | + message = "{} {}".format(message, charm_message) |
427 | + else: |
428 | + message = charm_message |
429 | + |
430 | + # Set to active if all requirements have been met |
431 | + if state == 'active': |
432 | + message = "Unit is ready" |
433 | + juju_log(message, "INFO") |
434 | + |
435 | + status_set(state, message) |
436 | + |
437 | + |
438 | +def workload_state_compare(current_workload_state, workload_state): |
439 | + """ Return highest priority of two states""" |
440 | + hierarchy = {'unknown': -1, |
441 | + 'active': 0, |
442 | + 'maintenance': 1, |
443 | + 'waiting': 2, |
444 | + 'blocked': 3, |
445 | + } |
446 | + |
447 | + if hierarchy.get(workload_state) is None: |
448 | + workload_state = 'unknown' |
449 | + if hierarchy.get(current_workload_state) is None: |
450 | + current_workload_state = 'unknown' |
451 | + |
452 | + # Set workload_state based on hierarchy of statuses |
453 | + if hierarchy.get(current_workload_state) > hierarchy.get(workload_state): |
454 | + return current_workload_state |
455 | + else: |
456 | + return workload_state |
457 | + |
458 | + |
459 | +def incomplete_relation_data(configs, required_interfaces): |
460 | + """ |
461 | + Check complete contexts against required_interfaces |
462 | + Return dictionary of incomplete relation data. |
463 | + |
464 | + configs is an OSConfigRenderer object with configs registered |
465 | + |
466 | + required_interfaces is a dictionary of required general interfaces |
467 | + with dictionary values of possible specific interfaces. |
468 | + Example: |
469 | + required_interfaces = {'database': ['shared-db', 'pgsql-db']} |
470 | + |
471 | + The interface is said to be satisfied if anyone of the interfaces in the |
472 | + list has a complete context. |
473 | + |
474 | + Return dictionary of incomplete or missing required contexts with relation |
475 | + status of interfaces and any missing data points. Example: |
476 | + {'message': |
477 | + {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True}, |
478 | + 'zeromq-configuration': {'related': False}}, |
479 | + 'identity': |
480 | + {'identity-service': {'related': False}}, |
481 | + 'database': |
482 | + {'pgsql-db': {'related': False}, |
483 | + 'shared-db': {'related': True}}} |
484 | + """ |
485 | + complete_ctxts = configs.complete_contexts() |
486 | + incomplete_relations = [] |
487 | + for svc_type in required_interfaces.keys(): |
488 | + # Avoid duplicates |
489 | + found_ctxt = False |
490 | + for interface in required_interfaces[svc_type]: |
491 | + if interface in complete_ctxts: |
492 | + found_ctxt = True |
493 | + if not found_ctxt: |
494 | + incomplete_relations.append(svc_type) |
495 | + incomplete_context_data = {} |
496 | + for i in incomplete_relations: |
497 | + incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i]) |
498 | + return incomplete_context_data |
499 | + |
500 | + |
501 | def do_action_openstack_upgrade(package, upgrade_callback, configs): |
502 | """Perform action-managed OpenStack upgrade. |
503 | |
504 | |
505 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
506 | --- hooks/charmhelpers/core/hookenv.py 2015-09-03 09:43:34 +0000 |
507 | +++ hooks/charmhelpers/core/hookenv.py 2015-09-27 18:46:57 +0000 |
508 | @@ -623,6 +623,38 @@ |
509 | return unit_get('private-address') |
510 | |
511 | |
512 | +@cached |
513 | +def storage_get(attribute="", storage_id=""): |
514 | + """Get storage attributes""" |
515 | + _args = ['storage-get', '--format=json'] |
516 | + if storage_id: |
517 | + _args.extend(('-s', storage_id)) |
518 | + if attribute: |
519 | + _args.append(attribute) |
520 | + try: |
521 | + return json.loads(subprocess.check_output(_args).decode('UTF-8')) |
522 | + except ValueError: |
523 | + return None |
524 | + |
525 | + |
526 | +@cached |
527 | +def storage_list(storage_name=""): |
528 | + """List the storage IDs for the unit""" |
529 | + _args = ['storage-list', '--format=json'] |
530 | + if storage_name: |
531 | + _args.append(storage_name) |
532 | + try: |
533 | + return json.loads(subprocess.check_output(_args).decode('UTF-8')) |
534 | + except ValueError: |
535 | + return None |
536 | + except OSError as e: |
537 | + import errno |
538 | + if e.errno == errno.ENOENT: |
539 | + # storage-list does not exist |
540 | + return [] |
541 | + raise |
542 | + |
543 | + |
544 | class UnregisteredHookError(Exception): |
545 | """Raised when an undefined hook is called""" |
546 | pass |
547 | |
548 | === modified file 'hooks/charmhelpers/core/hugepage.py' |
549 | --- hooks/charmhelpers/core/hugepage.py 2015-08-19 13:51:56 +0000 |
550 | +++ hooks/charmhelpers/core/hugepage.py 2015-09-27 18:46:57 +0000 |
551 | @@ -25,11 +25,13 @@ |
552 | fstab_mount, |
553 | mkdir, |
554 | ) |
555 | +from charmhelpers.core.strutils import bytes_from_string |
556 | +from subprocess import check_output |
557 | |
558 | |
559 | def hugepage_support(user, group='hugetlb', nr_hugepages=256, |
560 | max_map_count=65536, mnt_point='/run/hugepages/kvm', |
561 | - pagesize='2MB', mount=True): |
562 | + pagesize='2MB', mount=True, set_shmmax=False): |
563 | """Enable hugepages on system. |
564 | |
565 | Args: |
566 | @@ -49,6 +51,11 @@ |
567 | 'vm.max_map_count': max_map_count, |
568 | 'vm.hugetlb_shm_group': gid, |
569 | } |
570 | + if set_shmmax: |
571 | + shmmax_current = int(check_output(['sysctl', '-n', 'kernel.shmmax'])) |
572 | + shmmax_minsize = bytes_from_string(pagesize) * nr_hugepages |
573 | + if shmmax_minsize > shmmax_current: |
574 | + sysctl_settings['kernel.shmmax'] = shmmax_minsize |
575 | sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf') |
576 | mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False) |
577 | lfstab = fstab.Fstab() |
578 | |
579 | === modified file 'hooks/charmhelpers/core/strutils.py' |
580 | --- hooks/charmhelpers/core/strutils.py 2015-04-13 19:41:31 +0000 |
581 | +++ hooks/charmhelpers/core/strutils.py 2015-09-27 18:46:57 +0000 |
582 | @@ -18,6 +18,7 @@ |
583 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
584 | |
585 | import six |
586 | +import re |
587 | |
588 | |
589 | def bool_from_string(value): |
590 | @@ -40,3 +41,32 @@ |
591 | |
592 | msg = "Unable to interpret string value '%s' as boolean" % (value) |
593 | raise ValueError(msg) |
594 | + |
595 | + |
596 | +def bytes_from_string(value): |
597 | + """Interpret human readable string value as bytes. |
598 | + |
599 | + Returns int |
600 | + """ |
601 | + BYTE_POWER = { |
602 | + 'K': 1, |
603 | + 'KB': 1, |
604 | + 'M': 2, |
605 | + 'MB': 2, |
606 | + 'G': 3, |
607 | + 'GB': 3, |
608 | + 'T': 4, |
609 | + 'TB': 4, |
610 | + 'P': 5, |
611 | + 'PB': 5, |
612 | + } |
613 | + if isinstance(value, six.string_types): |
614 | + value = six.text_type(value) |
615 | + else: |
616 | + msg = "Unable to interpret non-string value '%s' as boolean" % (value) |
617 | + raise ValueError(msg) |
618 | + matches = re.match("([0-9]+)([a-zA-Z]+)", value) |
619 | + if not matches: |
620 | + msg = "Unable to interpret string value '%s' as bytes" % (value) |
621 | + raise ValueError(msg) |
622 | + return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) |
623 | |
624 | === modified file 'tests/charmhelpers/contrib/amulet/deployment.py' |
625 | --- tests/charmhelpers/contrib/amulet/deployment.py 2015-01-26 09:47:37 +0000 |
626 | +++ tests/charmhelpers/contrib/amulet/deployment.py 2015-09-27 18:46:57 +0000 |
627 | @@ -51,7 +51,8 @@ |
628 | if 'units' not in this_service: |
629 | this_service['units'] = 1 |
630 | |
631 | - self.d.add(this_service['name'], units=this_service['units']) |
632 | + self.d.add(this_service['name'], units=this_service['units'], |
633 | + constraints=this_service.get('constraints')) |
634 | |
635 | for svc in other_services: |
636 | if 'location' in svc: |
637 | @@ -64,7 +65,8 @@ |
638 | if 'units' not in svc: |
639 | svc['units'] = 1 |
640 | |
641 | - self.d.add(svc['name'], charm=branch_location, units=svc['units']) |
642 | + self.d.add(svc['name'], charm=branch_location, units=svc['units'], |
643 | + constraints=svc.get('constraints')) |
644 | |
645 | def _add_relations(self, relations): |
646 | """Add all of the relations for the services.""" |
647 | |
648 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' |
649 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-09-14 20:19:57 +0000 |
650 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-27 18:46:57 +0000 |
651 | @@ -326,7 +326,7 @@ |
652 | |
653 | def service_restarted_since(self, sentry_unit, mtime, service, |
654 | pgrep_full=None, sleep_time=20, |
655 | - retry_count=2, retry_sleep_time=30): |
656 | + retry_count=30, retry_sleep_time=10): |
657 | """Check if service was been started after a given time. |
658 | |
659 | Args: |
660 | @@ -334,8 +334,9 @@ |
661 | mtime (float): The epoch time to check against |
662 | service (string): service name to look for in process table |
663 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
664 | - sleep_time (int): Seconds to sleep before looking for process |
665 | - retry_count (int): If service is not found, how many times to retry |
666 | + sleep_time (int): Initial sleep time (s) before looking for file |
667 | + retry_sleep_time (int): Time (s) to sleep between retries |
668 | + retry_count (int): If file is not found, how many times to retry |
669 | |
670 | Returns: |
671 | bool: True if service found and its start time it newer than mtime, |
672 | @@ -359,11 +360,12 @@ |
673 | pgrep_full) |
674 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
675 | 'OK'.format(tries, service, unit_name)) |
676 | - except IOError: |
677 | + except IOError as e: |
678 | # NOTE(beisner) - race avoidance, proc may not exist yet. |
679 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 |
680 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
681 | - 'failed'.format(tries, service, unit_name)) |
682 | + 'failed\n{}'.format(tries, service, |
683 | + unit_name, e)) |
684 | time.sleep(retry_sleep_time) |
685 | tries += 1 |
686 | |
687 | @@ -383,35 +385,62 @@ |
688 | return False |
689 | |
690 | def config_updated_since(self, sentry_unit, filename, mtime, |
691 | - sleep_time=20): |
692 | + sleep_time=20, retry_count=30, |
693 | + retry_sleep_time=10): |
694 | """Check if file was modified after a given time. |
695 | |
696 | Args: |
697 | sentry_unit (sentry): The sentry unit to check the file mtime on |
698 | filename (string): The file to check mtime of |
699 | mtime (float): The epoch time to check against |
700 | - sleep_time (int): Seconds to sleep before looking for process |
701 | + sleep_time (int): Initial sleep time (s) before looking for file |
702 | + retry_sleep_time (int): Time (s) to sleep between retries |
703 | + retry_count (int): If file is not found, how many times to retry |
704 | |
705 | Returns: |
706 | bool: True if file was modified more recently than mtime, False if |
707 | - file was modified before mtime, |
708 | + file was modified before mtime, or if file not found. |
709 | """ |
710 | - self.log.debug('Checking %s updated since %s' % (filename, mtime)) |
711 | + unit_name = sentry_unit.info['unit_name'] |
712 | + self.log.debug('Checking that %s updated since %s on ' |
713 | + '%s' % (filename, mtime, unit_name)) |
714 | time.sleep(sleep_time) |
715 | - file_mtime = self._get_file_mtime(sentry_unit, filename) |
716 | + file_mtime = None |
717 | + tries = 0 |
718 | + while tries <= retry_count and not file_mtime: |
719 | + try: |
720 | + file_mtime = self._get_file_mtime(sentry_unit, filename) |
721 | + self.log.debug('Attempt {} to get {} file mtime on {} ' |
722 | + 'OK'.format(tries, filename, unit_name)) |
723 | + except IOError as e: |
724 | + # NOTE(beisner) - race avoidance, file may not exist yet. |
725 | + # https://bugs.launchpad.net/charm-helpers/+bug/1474030 |
726 | + self.log.debug('Attempt {} to get {} file mtime on {} ' |
727 | + 'failed\n{}'.format(tries, filename, |
728 | + unit_name, e)) |
729 | + time.sleep(retry_sleep_time) |
730 | + tries += 1 |
731 | + |
732 | + if not file_mtime: |
733 | + self.log.warn('Could not determine file mtime, assuming ' |
734 | + 'file does not exist') |
735 | + return False |
736 | + |
737 | if file_mtime >= mtime: |
738 | self.log.debug('File mtime is newer than provided mtime ' |
739 | - '(%s >= %s)' % (file_mtime, mtime)) |
740 | + '(%s >= %s) on %s (OK)' % (file_mtime, |
741 | + mtime, unit_name)) |
742 | return True |
743 | else: |
744 | - self.log.warn('File mtime %s is older than provided mtime %s' |
745 | - % (file_mtime, mtime)) |
746 | + self.log.warn('File mtime is older than provided mtime' |
747 | + '(%s < on %s) on %s' % (file_mtime, |
748 | + mtime, unit_name)) |
749 | return False |
750 | |
751 | def validate_service_config_changed(self, sentry_unit, mtime, service, |
752 | filename, pgrep_full=None, |
753 | - sleep_time=20, retry_count=2, |
754 | - retry_sleep_time=30): |
755 | + sleep_time=20, retry_count=30, |
756 | + retry_sleep_time=10): |
757 | """Check service and file were updated after mtime |
758 | |
759 | Args: |
760 | @@ -456,7 +485,9 @@ |
761 | sentry_unit, |
762 | filename, |
763 | mtime, |
764 | - sleep_time=0) |
765 | + sleep_time=sleep_time, |
766 | + retry_count=retry_count, |
767 | + retry_sleep_time=retry_sleep_time) |
768 | |
769 | return service_restart and config_update |
770 | |
771 | |
772 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
773 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-10 09:30:00 +0000 |
774 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-27 18:46:57 +0000 |
775 | @@ -58,19 +58,17 @@ |
776 | else: |
777 | base_series = self.current_next |
778 | |
779 | - if self.stable: |
780 | - for svc in other_services: |
781 | - if svc['name'] in force_series_current: |
782 | - base_series = self.current_next |
783 | - |
784 | + for svc in other_services: |
785 | + if svc['name'] in force_series_current: |
786 | + base_series = self.current_next |
787 | + # If a location has been explicitly set, use it |
788 | + if svc.get('location'): |
789 | + continue |
790 | + if self.stable: |
791 | temp = 'lp:charms/{}/{}' |
792 | svc['location'] = temp.format(base_series, |
793 | svc['name']) |
794 | - else: |
795 | - for svc in other_services: |
796 | - if svc['name'] in force_series_current: |
797 | - base_series = self.current_next |
798 | - |
799 | + else: |
800 | if svc['name'] in base_charms: |
801 | temp = 'lp:charms/{}/{}' |
802 | svc['location'] = temp.format(base_series, |
803 | @@ -79,6 +77,7 @@ |
804 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
805 | svc['location'] = temp.format(self.current_next, |
806 | svc['name']) |
807 | + |
808 | return other_services |
809 | |
810 | def _add_services(self, this_service, other_services): |
charm_lint_check #10886 cinder-next for hopem mp272532
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/10886/