Merge lp:~hopem/charms/trusty/cinder/lp1499643 into lp:~openstack-charmers-archive/charms/trusty/cinder/next

Proposed by Edward Hope-Morley on 2015-09-27
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
Reviewer Review Type Date Requested Status
Liam Young 2015-09-27 Approve on 2015-09-28
Review via email: mp+272532@code.launchpad.net
To post a comment you must log in.

charm_lint_check #10886 cinder-next for hopem mp272532
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/10886/

charm_unit_test #10058 cinder-next for hopem mp272532
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/10058/

Liam Young (gnuoy) wrote :

Approve

review: Approve

charm_amulet_test #6841 cinder-next for hopem mp272532
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6841/

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):

Subscribers

People subscribed via source and target branches