Merge lp:~1chb1n/charms/trusty/neutron-openvswitch/next-amulet-15.11 into lp:~openstack-charmers-archive/charms/trusty/neutron-openvswitch/next
- Trusty Tahr (14.04)
- next-amulet-15.11
- Merge into next
Proposed by
Ryan Beisner
Status: | Work in progress |
---|---|
Proposed branch: | lp:~1chb1n/charms/trusty/neutron-openvswitch/next-amulet-15.11 |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/neutron-openvswitch/next |
Diff against target: |
1115 lines (+530/-92) 17 files modified
Makefile (+1/-0) hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1) hooks/charmhelpers/contrib/openstack/amulet/utils.py (+26/-4) hooks/charmhelpers/contrib/openstack/context.py (+25/-9) hooks/charmhelpers/contrib/openstack/neutron.py (+14/-0) hooks/charmhelpers/contrib/openstack/utils.py (+4/-1) hooks/charmhelpers/core/hookenv.py (+14/-0) hooks/charmhelpers/core/host.py (+28/-1) hooks/charmhelpers/core/hugepage.py (+2/-0) hooks/charmhelpers/core/templating.py (+5/-1) hooks/charmhelpers/fetch/__init__.py (+1/-1) tests/020-basic-trusty-liberty (+11/-0) tests/021-basic-wily-liberty (+9/-0) tests/README (+10/-0) tests/basic_deployment.py (+155/-70) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1) tests/charmhelpers/contrib/openstack/amulet/utils.py (+25/-3) |
To merge this branch: | bzr merge lp:~1chb1n/charms/trusty/neutron-openvswitch/next-amulet-15.11 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack Charmers | Pending | ||
Review via email: mp+275767@code.launchpad.net |
Commit message
Description of the change
Update amulet tests for Liberty. Sync charm helpers. Wait for workload status before testing.
To post a comment you must log in.
- 97. By Ryan Beisner
-
update tests
Unmerged revisions
- 97. By Ryan Beisner
-
update tests
- 96. By Ryan Beisner
-
update test deployment topology to satisfy workload status; wait for ready state before testing.
- 95. By Ryan Beisner
-
fix old lint
- 94. By Ryan Beisner
-
update readme; add liberty amuelt test definitions; move 00-setup to prevent superfluous bootstrap/destroy iteration.
- 93. By Ryan Beisner
-
sync charmhelpers
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2015-10-06 16:23:59 +0000 |
3 | +++ Makefile 2015-10-26 21:25:29 +0000 |
4 | @@ -13,6 +13,7 @@ |
5 | |
6 | functional_test: |
7 | @echo Starting Amulet tests... |
8 | + @tests/setup/00-setup |
9 | @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700 |
10 | |
11 | bin/charm_helpers_sync.py: |
12 | |
13 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' |
14 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-21 22:49:35 +0000 |
15 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-26 21:25:29 +0000 |
16 | @@ -14,12 +14,18 @@ |
17 | # You should have received a copy of the GNU Lesser General Public License |
18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
19 | |
20 | +import logging |
21 | +import re |
22 | +import sys |
23 | import six |
24 | from collections import OrderedDict |
25 | from charmhelpers.contrib.amulet.deployment import ( |
26 | AmuletDeployment |
27 | ) |
28 | |
29 | +DEBUG = logging.DEBUG |
30 | +ERROR = logging.ERROR |
31 | + |
32 | |
33 | class OpenStackAmuletDeployment(AmuletDeployment): |
34 | """OpenStack amulet deployment. |
35 | @@ -28,9 +34,12 @@ |
36 | that is specifically for use by OpenStack charms. |
37 | """ |
38 | |
39 | - def __init__(self, series=None, openstack=None, source=None, stable=True): |
40 | + def __init__(self, series=None, openstack=None, source=None, |
41 | + stable=True, log_level=DEBUG): |
42 | """Initialize the deployment environment.""" |
43 | super(OpenStackAmuletDeployment, self).__init__(series) |
44 | + self.log = self.get_logger(level=log_level) |
45 | + self.log.info('OpenStackAmuletDeployment: init') |
46 | self.openstack = openstack |
47 | self.source = source |
48 | self.stable = stable |
49 | @@ -38,6 +47,22 @@ |
50 | # out. |
51 | self.current_next = "trusty" |
52 | |
53 | + def get_logger(self, name="deployment-logger", level=logging.DEBUG): |
54 | + """Get a logger object that will log to stdout.""" |
55 | + log = logging |
56 | + logger = log.getLogger(name) |
57 | + fmt = log.Formatter("%(asctime)s %(funcName)s " |
58 | + "%(levelname)s: %(message)s") |
59 | + |
60 | + handler = log.StreamHandler(stream=sys.stdout) |
61 | + handler.setLevel(level) |
62 | + handler.setFormatter(fmt) |
63 | + |
64 | + logger.addHandler(handler) |
65 | + logger.setLevel(level) |
66 | + |
67 | + return logger |
68 | + |
69 | def _determine_branch_locations(self, other_services): |
70 | """Determine the branch locations for the other services. |
71 | |
72 | @@ -45,6 +70,8 @@ |
73 | stable or next (dev) branch, and based on this, use the corresonding |
74 | stable or next branches for the other_services.""" |
75 | |
76 | + self.log.info('OpenStackAmuletDeployment: determine branch locations') |
77 | + |
78 | # Charms outside the lp:~openstack-charmers namespace |
79 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
80 | |
81 | @@ -82,6 +109,8 @@ |
82 | |
83 | def _add_services(self, this_service, other_services): |
84 | """Add services to the deployment and set openstack-origin/source.""" |
85 | + self.log.info('OpenStackAmuletDeployment: adding services') |
86 | + |
87 | other_services = self._determine_branch_locations(other_services) |
88 | |
89 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
90 | @@ -111,9 +140,79 @@ |
91 | |
92 | def _configure_services(self, configs): |
93 | """Configure all of the services.""" |
94 | + self.log.info('OpenStackAmuletDeployment: configure services') |
95 | for service, config in six.iteritems(configs): |
96 | self.d.configure(service, config) |
97 | |
98 | + def _auto_wait_for_status(self, message=None, exclude_services=None, |
99 | + include_only=None, timeout=1800): |
100 | + """Wait for all units to have a specific extended status, except |
101 | + for any defined as excluded. Unless specified via message, any |
102 | + status containing any case of 'ready' will be considered a match. |
103 | + |
104 | + Examples of message usage: |
105 | + |
106 | + Wait for all unit status to CONTAIN any case of 'ready' or 'ok': |
107 | + message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) |
108 | + |
109 | + Wait for all units to reach this status (exact match): |
110 | + message = re.compile('^Unit is ready and clustered$') |
111 | + |
112 | + Wait for all units to reach any one of these (exact match): |
113 | + message = re.compile('Unit is ready|OK|Ready') |
114 | + |
115 | + Wait for at least one unit to reach this status (exact match): |
116 | + message = {'ready'} |
117 | + |
118 | + See Amulet's sentry.wait_for_messages() for message usage detail. |
119 | + https://github.com/juju/amulet/blob/master/amulet/sentry.py |
120 | + |
121 | + :param message: Expected status match |
122 | + :param exclude_services: List of juju service names to ignore, |
123 | + not to be used in conjuction with include_only. |
124 | + :param include_only: List of juju service names to exclusively check, |
125 | + not to be used in conjuction with exclude_services. |
126 | + :param timeout: Maximum time in seconds to wait for status match |
127 | + :returns: None. Raises if timeout is hit. |
128 | + """ |
129 | + self.log.info('Waiting for extended status on units...') |
130 | + |
131 | + all_services = self.d.services.keys() |
132 | + |
133 | + if exclude_services and include_only: |
134 | + raise ValueError('exclude_services can not be used ' |
135 | + 'with include_only') |
136 | + |
137 | + if message: |
138 | + if isinstance(message, re._pattern_type): |
139 | + match = message.pattern |
140 | + else: |
141 | + match = message |
142 | + |
143 | + self.log.debug('Custom extended status wait match: ' |
144 | + '{}'.format(match)) |
145 | + else: |
146 | + self.log.debug('Default extended status wait match: contains ' |
147 | + 'READY (case-insensitive)') |
148 | + message = re.compile('.*ready.*', re.IGNORECASE) |
149 | + |
150 | + if exclude_services: |
151 | + self.log.debug('Excluding services from extended status match: ' |
152 | + '{}'.format(exclude_services)) |
153 | + else: |
154 | + exclude_services = [] |
155 | + |
156 | + if include_only: |
157 | + services = include_only |
158 | + else: |
159 | + services = list(set(all_services) - set(exclude_services)) |
160 | + |
161 | + self.log.debug('Waiting up to {}s for extended status on services: ' |
162 | + '{}'.format(timeout, services)) |
163 | + service_messages = {service: message for service in services} |
164 | + self.d.sentry.wait_for_messages(service_messages, timeout=timeout) |
165 | + self.log.info('OK') |
166 | + |
167 | def _get_openstack_release(self): |
168 | """Get openstack release. |
169 | |
170 | |
171 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' |
172 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-21 22:49:35 +0000 |
173 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-26 21:25:29 +0000 |
174 | @@ -18,6 +18,7 @@ |
175 | import json |
176 | import logging |
177 | import os |
178 | +import re |
179 | import six |
180 | import time |
181 | import urllib |
182 | @@ -604,7 +605,22 @@ |
183 | '{}'.format(sample_type, samples)) |
184 | return None |
185 | |
186 | -# rabbitmq/amqp specific helpers: |
187 | + # rabbitmq/amqp specific helpers: |
188 | + |
189 | + def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): |
190 | + """Wait for rmq units extended status to show cluster readiness, |
191 | + after an optional initial sleep period. Initial sleep is likely |
192 | + necessary to be effective following a config change, as status |
193 | + message may not instantly update to non-ready.""" |
194 | + |
195 | + if init_sleep: |
196 | + time.sleep(init_sleep) |
197 | + |
198 | + message = re.compile('^Unit is ready and clustered$') |
199 | + deployment._auto_wait_for_status(message=message, |
200 | + timeout=timeout, |
201 | + include_only=['rabbitmq-server']) |
202 | + |
203 | def add_rmq_test_user(self, sentry_units, |
204 | username="testuser1", password="changeme"): |
205 | """Add a test user via the first rmq juju unit, check connection as |
206 | @@ -752,7 +768,7 @@ |
207 | self.log.debug('SSL is enabled @{}:{} ' |
208 | '({})'.format(host, port, unit_name)) |
209 | return True |
210 | - elif not port and not conf_ssl: |
211 | + elif not conf_ssl: |
212 | self.log.debug('SSL not enabled @{}:{} ' |
213 | '({})'.format(host, port, unit_name)) |
214 | return False |
215 | @@ -805,7 +821,10 @@ |
216 | if port: |
217 | config['ssl_port'] = port |
218 | |
219 | - deployment.configure('rabbitmq-server', config) |
220 | + deployment.d.configure('rabbitmq-server', config) |
221 | + |
222 | + # Wait for unit status |
223 | + self.rmq_wait_for_cluster(deployment) |
224 | |
225 | # Confirm |
226 | tries = 0 |
227 | @@ -832,7 +851,10 @@ |
228 | |
229 | # Disable RMQ SSL |
230 | config = {'ssl': 'off'} |
231 | - deployment.configure('rabbitmq-server', config) |
232 | + deployment.d.configure('rabbitmq-server', config) |
233 | + |
234 | + # Wait for unit status |
235 | + self.rmq_wait_for_cluster(deployment) |
236 | |
237 | # Confirm |
238 | tries = 0 |
239 | |
240 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' |
241 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-09-29 20:59:00 +0000 |
242 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-10-26 21:25:29 +0000 |
243 | @@ -952,6 +952,19 @@ |
244 | 'config': config} |
245 | return ovs_ctxt |
246 | |
247 | + def midonet_ctxt(self): |
248 | + driver = neutron_plugin_attribute(self.plugin, 'driver', |
249 | + self.network_manager) |
250 | + midonet_config = neutron_plugin_attribute(self.plugin, 'config', |
251 | + self.network_manager) |
252 | + mido_ctxt = {'core_plugin': driver, |
253 | + 'neutron_plugin': 'midonet', |
254 | + 'neutron_security_groups': self.neutron_security_groups, |
255 | + 'local_ip': unit_private_ip(), |
256 | + 'config': midonet_config} |
257 | + |
258 | + return mido_ctxt |
259 | + |
260 | def __call__(self): |
261 | if self.network_manager not in ['quantum', 'neutron']: |
262 | return {} |
263 | @@ -973,6 +986,8 @@ |
264 | ctxt.update(self.nuage_ctxt()) |
265 | elif self.plugin == 'plumgrid': |
266 | ctxt.update(self.pg_ctxt()) |
267 | + elif self.plugin == 'midonet': |
268 | + ctxt.update(self.midonet_ctxt()) |
269 | |
270 | alchemy_flags = config('neutron-alchemy-flags') |
271 | if alchemy_flags: |
272 | @@ -1105,7 +1120,7 @@ |
273 | |
274 | ctxt = { |
275 | ... other context ... |
276 | - 'subordinate_config': { |
277 | + 'subordinate_configuration': { |
278 | 'DEFAULT': { |
279 | 'key1': 'value1', |
280 | }, |
281 | @@ -1146,22 +1161,23 @@ |
282 | try: |
283 | sub_config = json.loads(sub_config) |
284 | except: |
285 | - log('Could not parse JSON from subordinate_config ' |
286 | - 'setting from %s' % rid, level=ERROR) |
287 | + log('Could not parse JSON from ' |
288 | + 'subordinate_configuration setting from %s' |
289 | + % rid, level=ERROR) |
290 | continue |
291 | |
292 | for service in self.services: |
293 | if service not in sub_config: |
294 | - log('Found subordinate_config on %s but it contained' |
295 | - 'nothing for %s service' % (rid, service), |
296 | - level=INFO) |
297 | + log('Found subordinate_configuration on %s but it ' |
298 | + 'contained nothing for %s service' |
299 | + % (rid, service), level=INFO) |
300 | continue |
301 | |
302 | sub_config = sub_config[service] |
303 | if self.config_file not in sub_config: |
304 | - log('Found subordinate_config on %s but it contained' |
305 | - 'nothing for %s' % (rid, self.config_file), |
306 | - level=INFO) |
307 | + log('Found subordinate_configuration on %s but it ' |
308 | + 'contained nothing for %s' |
309 | + % (rid, self.config_file), level=INFO) |
310 | continue |
311 | |
312 | sub_config = sub_config[self.config_file] |
313 | |
314 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' |
315 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2015-09-28 11:32:27 +0000 |
316 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-10-26 21:25:29 +0000 |
317 | @@ -209,6 +209,20 @@ |
318 | 'server_packages': ['neutron-server', |
319 | 'neutron-plugin-plumgrid'], |
320 | 'server_services': ['neutron-server'] |
321 | + }, |
322 | + 'midonet': { |
323 | + 'config': '/etc/neutron/plugins/midonet/midonet.ini', |
324 | + 'driver': 'midonet.neutron.plugin.MidonetPluginV2', |
325 | + 'contexts': [ |
326 | + context.SharedDBContext(user=config('neutron-database-user'), |
327 | + database=config('neutron-database'), |
328 | + relation_prefix='neutron', |
329 | + ssl_dir=NEUTRON_CONF_DIR)], |
330 | + 'services': [], |
331 | + 'packages': [[headers_package()] + determine_dkms_package()], |
332 | + 'server_packages': ['neutron-server', |
333 | + 'python-neutron-plugin-midonet'], |
334 | + 'server_services': ['neutron-server'] |
335 | } |
336 | } |
337 | if release >= 'icehouse': |
338 | |
339 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' |
340 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-09-28 11:32:27 +0000 |
341 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2015-10-26 21:25:29 +0000 |
342 | @@ -121,6 +121,7 @@ |
343 | ('2.2.2', 'kilo'), |
344 | ('2.3.0', 'liberty'), |
345 | ('2.4.0', 'liberty'), |
346 | + ('2.5.0', 'liberty'), |
347 | ]) |
348 | |
349 | # >= Liberty version->codename mapping |
350 | @@ -858,7 +859,9 @@ |
351 | if charm_state != 'active' and charm_state != 'unknown': |
352 | state = workload_state_compare(state, charm_state) |
353 | if message: |
354 | - message = "{} {}".format(message, charm_message) |
355 | + charm_message = charm_message.replace("Incomplete relations: ", |
356 | + "") |
357 | + message = "{}, {}".format(message, charm_message) |
358 | else: |
359 | message = charm_message |
360 | |
361 | |
362 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
363 | --- hooks/charmhelpers/core/hookenv.py 2015-09-28 11:32:27 +0000 |
364 | +++ hooks/charmhelpers/core/hookenv.py 2015-10-26 21:25:29 +0000 |
365 | @@ -491,6 +491,19 @@ |
366 | |
367 | |
368 | @cached |
369 | +def peer_relation_id(): |
370 | + '''Get a peer relation id if a peer relation has been joined, else None.''' |
371 | + md = metadata() |
372 | + section = md.get('peers') |
373 | + if section: |
374 | + for key in section: |
375 | + relids = relation_ids(key) |
376 | + if relids: |
377 | + return relids[0] |
378 | + return None |
379 | + |
380 | + |
381 | +@cached |
382 | def relation_to_interface(relation_name): |
383 | """ |
384 | Given the name of a relation, return the interface that relation uses. |
385 | @@ -820,6 +833,7 @@ |
386 | |
387 | def translate_exc(from_exc, to_exc): |
388 | def inner_translate_exc1(f): |
389 | + @wraps(f) |
390 | def inner_translate_exc2(*args, **kwargs): |
391 | try: |
392 | return f(*args, **kwargs) |
393 | |
394 | === modified file 'hooks/charmhelpers/core/host.py' |
395 | --- hooks/charmhelpers/core/host.py 2015-09-21 22:49:35 +0000 |
396 | +++ hooks/charmhelpers/core/host.py 2015-10-26 21:25:29 +0000 |
397 | @@ -566,7 +566,14 @@ |
398 | os.chdir(cur) |
399 | |
400 | |
401 | -def chownr(path, owner, group, follow_links=True): |
402 | +def chownr(path, owner, group, follow_links=True, chowntopdir=False): |
403 | + """ |
404 | + Recursively change user and group ownership of files and directories |
405 | + in given path. Doesn't chown path itself by default, only its children. |
406 | + |
407 | + :param bool follow_links: Also Chown links if True |
408 | + :param bool chowntopdir: Also chown path itself if True |
409 | + """ |
410 | uid = pwd.getpwnam(owner).pw_uid |
411 | gid = grp.getgrnam(group).gr_gid |
412 | if follow_links: |
413 | @@ -574,6 +581,10 @@ |
414 | else: |
415 | chown = os.lchown |
416 | |
417 | + if chowntopdir: |
418 | + broken_symlink = os.path.lexists(path) and not os.path.exists(path) |
419 | + if not broken_symlink: |
420 | + chown(path, uid, gid) |
421 | for root, dirs, files in os.walk(path): |
422 | for name in dirs + files: |
423 | full = os.path.join(root, name) |
424 | @@ -584,3 +595,19 @@ |
425 | |
426 | def lchownr(path, owner, group): |
427 | chownr(path, owner, group, follow_links=False) |
428 | + |
429 | + |
430 | +def get_total_ram(): |
431 | + '''The total amount of system RAM in bytes. |
432 | + |
433 | + This is what is reported by the OS, and may be overcommitted when |
434 | + there are multiple containers hosted on the same machine. |
435 | + ''' |
436 | + with open('/proc/meminfo', 'r') as f: |
437 | + for line in f.readlines(): |
438 | + if line: |
439 | + key, value, unit = line.split() |
440 | + if key == 'MemTotal:': |
441 | + assert unit == 'kB', 'Unknown unit' |
442 | + return int(value) * 1024 # Classic, not KiB. |
443 | + raise NotImplementedError() |
444 | |
445 | === modified file 'hooks/charmhelpers/core/hugepage.py' |
446 | --- hooks/charmhelpers/core/hugepage.py 2015-09-21 22:49:35 +0000 |
447 | +++ hooks/charmhelpers/core/hugepage.py 2015-10-26 21:25:29 +0000 |
448 | @@ -46,6 +46,8 @@ |
449 | group_info = add_group(group) |
450 | gid = group_info.gr_gid |
451 | add_user_to_group(user, group) |
452 | + if max_map_count < 2 * nr_hugepages: |
453 | + max_map_count = 2 * nr_hugepages |
454 | sysctl_settings = { |
455 | 'vm.nr_hugepages': nr_hugepages, |
456 | 'vm.max_map_count': max_map_count, |
457 | |
458 | === modified file 'hooks/charmhelpers/core/templating.py' |
459 | --- hooks/charmhelpers/core/templating.py 2015-02-11 18:54:05 +0000 |
460 | +++ hooks/charmhelpers/core/templating.py 2015-10-26 21:25:29 +0000 |
461 | @@ -64,5 +64,9 @@ |
462 | level=hookenv.ERROR) |
463 | raise e |
464 | content = template.render(context) |
465 | - host.mkdir(os.path.dirname(target), owner, group, perms=0o755) |
466 | + target_dir = os.path.dirname(target) |
467 | + if not os.path.exists(target_dir): |
468 | + # This is a terrible default directory permission, as the file |
469 | + # or its siblings will often contain secrets. |
470 | + host.mkdir(os.path.dirname(target), owner, group, perms=0o755) |
471 | host.write_file(target, content.encode(encoding), owner, group, perms) |
472 | |
473 | === modified file 'hooks/charmhelpers/fetch/__init__.py' |
474 | --- hooks/charmhelpers/fetch/__init__.py 2015-08-18 21:19:31 +0000 |
475 | +++ hooks/charmhelpers/fetch/__init__.py 2015-10-26 21:25:29 +0000 |
476 | @@ -225,12 +225,12 @@ |
477 | |
478 | def apt_mark(packages, mark, fatal=False): |
479 | """Flag one or more packages using apt-mark""" |
480 | + log("Marking {} as {}".format(packages, mark)) |
481 | cmd = ['apt-mark', mark] |
482 | if isinstance(packages, six.string_types): |
483 | cmd.append(packages) |
484 | else: |
485 | cmd.extend(packages) |
486 | - log("Holding {}".format(packages)) |
487 | |
488 | if fatal: |
489 | subprocess.check_call(cmd, universal_newlines=True) |
490 | |
491 | === modified file 'tests/019-basic-vivid-kilo' (properties changed: -x to +x) |
492 | === added file 'tests/020-basic-trusty-liberty' |
493 | --- tests/020-basic-trusty-liberty 1970-01-01 00:00:00 +0000 |
494 | +++ tests/020-basic-trusty-liberty 2015-10-26 21:25:29 +0000 |
495 | @@ -0,0 +1,11 @@ |
496 | +#!/usr/bin/python |
497 | + |
498 | +"""Amulet tests on a basic neutron-openvswitch deployment on trusty-liberty.""" |
499 | + |
500 | +from basic_deployment import NeutronOVSBasicDeployment |
501 | + |
502 | +if __name__ == '__main__': |
503 | + deployment = NeutronOVSBasicDeployment(series='trusty', |
504 | + openstack='cloud:trusty-liberty', |
505 | + source='cloud:trusty-updates/liberty') |
506 | + deployment.run_tests() |
507 | |
508 | === added file 'tests/021-basic-wily-liberty' |
509 | --- tests/021-basic-wily-liberty 1970-01-01 00:00:00 +0000 |
510 | +++ tests/021-basic-wily-liberty 2015-10-26 21:25:29 +0000 |
511 | @@ -0,0 +1,9 @@ |
512 | +#!/usr/bin/python |
513 | + |
514 | +"""Amulet tests on a basic neutron-openvswitch deployment on wily-liberty.""" |
515 | + |
516 | +from basic_deployment import NeutronOVSBasicDeployment |
517 | + |
518 | +if __name__ == '__main__': |
519 | + deployment = NeutronOVSBasicDeployment(series='wily') |
520 | + deployment.run_tests() |
521 | |
522 | === modified file 'tests/README' |
523 | --- tests/README 2015-02-13 12:02:13 +0000 |
524 | +++ tests/README 2015-10-26 21:25:29 +0000 |
525 | @@ -1,6 +1,16 @@ |
526 | This directory provides Amulet tests that focus on verification of |
527 | neutron-openvswitch deployments. |
528 | |
529 | +test_* methods are called in lexical sort order, although each individual test |
530 | +should be idempotent, and expected to pass regardless of run order. |
531 | + |
532 | +Test name convention to ensure desired test order: |
533 | + 1xx service and endpoint checks |
534 | + 2xx relation checks |
535 | + 3xx config checks |
536 | + 4xx functional checks |
537 | + 9xx restarts and other final check |
538 | + |
539 | In order to run tests, you'll need charm-tools installed (in addition to |
540 | juju, of course): |
541 | sudo add-apt-repository ppa:juju/stable |
542 | |
543 | === modified file 'tests/basic_deployment.py' |
544 | --- tests/basic_deployment.py 2015-07-13 16:07:30 +0000 |
545 | +++ tests/basic_deployment.py 2015-10-26 21:25:29 +0000 |
546 | @@ -1,8 +1,6 @@ |
547 | -#!/usr/bin/python |
548 | - |
549 | import amulet |
550 | import os |
551 | -import time |
552 | +import re |
553 | import yaml |
554 | |
555 | from charmhelpers.contrib.openstack.amulet.deployment import ( |
556 | @@ -11,8 +9,8 @@ |
557 | |
558 | from charmhelpers.contrib.openstack.amulet.utils import ( |
559 | OpenStackAmuletUtils, |
560 | - DEBUG, # flake8: noqa |
561 | - ERROR |
562 | + DEBUG, |
563 | + # ERROR |
564 | ) |
565 | |
566 | # Use DEBUG to turn on debug logging |
567 | @@ -36,17 +34,30 @@ |
568 | self._add_relations() |
569 | self._configure_services() |
570 | self._deploy() |
571 | + self._wait_for_status() |
572 | self._initialize_tests() |
573 | |
574 | + def _wait_for_status(self): |
575 | + """Wait for unit extended status to indicate ready.""" |
576 | + u.log.info('Waiting on extended status checks...') |
577 | + message = re.compile('^Unit is ready$|^Unit is ready and clustered$', |
578 | + re.IGNORECASE) |
579 | + exclude_services = ['mysql'] |
580 | + self._auto_wait_for_status(message=message, |
581 | + exclude_services=exclude_services) |
582 | + |
583 | def _add_services(self): |
584 | """Add services |
585 | |
586 | - Add the services that we're testing, where neutron-openvswitch is local, |
587 | + Add the services to test, where neutron-openvswitch is local, |
588 | and the rest of the service are from lp branches that are |
589 | compatible with the local charm (e.g. stable or next). |
590 | """ |
591 | this_service = {'name': 'neutron-openvswitch'} |
592 | other_services = [{'name': 'nova-compute'}, |
593 | + {'name': 'keystone'}, # satisfy workload stat |
594 | + {'name': 'mysql'}, # satisfy workload stat |
595 | + {'name': 'glance'}, # satisfy workload stat |
596 | {'name': 'rabbitmq-server'}, |
597 | {'name': 'neutron-api'}] |
598 | super(NeutronOVSBasicDeployment, self)._add_services(this_service, |
599 | @@ -60,6 +71,16 @@ |
600 | 'nova-compute:neutron-plugin', |
601 | 'neutron-openvswitch:neutron-plugin-api': |
602 | 'neutron-api:neutron-plugin-api', |
603 | + # Satisfy workload stat: |
604 | + 'neutron-api:identity-service': 'keystone:identity-service', |
605 | + 'neutron-api:shared-db': 'mysql:shared-db', |
606 | + 'neutron-api:amqp': 'rabbitmq-server:amqp', |
607 | + 'nova-compute:amqp': 'rabbitmq-server:amqp', |
608 | + 'nova-compute:image-service': 'glance:image-service', |
609 | + 'glance:identity-service': 'keystone:identity-service', |
610 | + 'glance:shared-db': 'mysql:shared-db', |
611 | + 'glance:amqp': 'rabbitmq-server:amqp', |
612 | + 'keystone:shared-db': 'mysql:shared-db', |
613 | } |
614 | super(NeutronOVSBasicDeployment, self)._add_relations(relations) |
615 | |
616 | @@ -75,19 +96,24 @@ |
617 | openstack_origin_git = { |
618 | 'repositories': [ |
619 | {'name': 'requirements', |
620 | - 'repository': 'git://github.com/openstack/requirements', |
621 | + 'repository': |
622 | + 'git://github.com/openstack/requirements', |
623 | 'branch': branch}, |
624 | {'name': 'neutron-fwaas', |
625 | - 'repository': 'git://github.com/openstack/neutron-fwaas', |
626 | + 'repository': |
627 | + 'git://github.com/openstack/neutron-fwaas', |
628 | 'branch': branch}, |
629 | {'name': 'neutron-lbaas', |
630 | - 'repository': 'git://github.com/openstack/neutron-lbaas', |
631 | + 'repository': |
632 | + 'git://github.com/openstack/neutron-lbaas', |
633 | 'branch': branch}, |
634 | {'name': 'neutron-vpnaas', |
635 | - 'repository': 'git://github.com/openstack/neutron-vpnaas', |
636 | + 'repository': |
637 | + 'git://github.com/openstack/neutron-vpnaas', |
638 | 'branch': branch}, |
639 | {'name': 'neutron', |
640 | - 'repository': 'git://github.com/openstack/neutron', |
641 | + 'repository': |
642 | + 'git://github.com/openstack/neutron', |
643 | 'branch': branch}, |
644 | ], |
645 | 'directory': '/mnt/openstack-git', |
646 | @@ -114,7 +140,9 @@ |
647 | 'http_proxy': amulet_http_proxy, |
648 | 'https_proxy': amulet_http_proxy, |
649 | } |
650 | - neutron_ovs_config['openstack-origin-git'] = yaml.dump(openstack_origin_git) |
651 | + neutron_ovs_config['openstack-origin-git'] = \ |
652 | + yaml.dump(openstack_origin_git) |
653 | + |
654 | configs = {'neutron-openvswitch': neutron_ovs_config} |
655 | super(NeutronOVSBasicDeployment, self)._configure_services(configs) |
656 | |
657 | @@ -122,27 +150,34 @@ |
658 | """Perform final initialization before tests get run.""" |
659 | # Access the sentries for inspecting service units |
660 | self.compute_sentry = self.d.sentry.unit['nova-compute/0'] |
661 | - self.rabbitmq_sentry = self.d.sentry.unit['rabbitmq-server/0'] |
662 | - self.neutron_api_sentry = self.d.sentry.unit['neutron-api/0'] |
663 | + self.rmq_sentry = self.d.sentry.unit['rabbitmq-server/0'] |
664 | + self.keystone_sentry = self.d.sentry.unit['keystone/0'] |
665 | + self.mysql_sentry = self.d.sentry.unit['mysql/0'] |
666 | + self.n_api_sentry = self.d.sentry.unit['neutron-api/0'] |
667 | + self.n_ovs_sentry = self.d.sentry.unit['neutron-openvswitch/0'] |
668 | |
669 | - def test_services(self): |
670 | + def test_100_services(self): |
671 | """Verify the expected services are running on the corresponding |
672 | service units.""" |
673 | + u.log.debug('Checking system services on units...') |
674 | |
675 | - commands = { |
676 | - self.compute_sentry: ['status nova-compute', |
677 | - 'status neutron-plugin-openvswitch-agent'], |
678 | - self.rabbitmq_sentry: ['service rabbitmq-server status'], |
679 | - self.neutron_api_sentry: ['status neutron-server'], |
680 | + services = { |
681 | + self.compute_sentry: ['nova-compute', |
682 | + 'neutron-plugin-openvswitch-agent'], |
683 | + self.rmq_sentry: ['rabbitmq-server'], |
684 | + self.n_api_sentry: ['neutron-server'], |
685 | } |
686 | |
687 | - ret = u.validate_services(commands) |
688 | + ret = u.validate_services_by_name(services) |
689 | if ret: |
690 | amulet.raise_status(amulet.FAIL, msg=ret) |
691 | |
692 | - def test_rabbitmq_amqp_relation(self): |
693 | + u.log.debug('OK') |
694 | + |
695 | + def test_200_rmq_n_ovs_amqp_relation(self): |
696 | """Verify data in rabbitmq-server/neutron-openvswitch amqp relation""" |
697 | - unit = self.rabbitmq_sentry |
698 | + u.log.debug('Checking rmq:n-ovs amqp relation data...') |
699 | + unit = self.rmq_sentry |
700 | relation = ['amqp', 'neutron-openvswitch:amqp'] |
701 | expected = { |
702 | 'private-address': u.valid_ip, |
703 | @@ -155,8 +190,12 @@ |
704 | message = u.relation_error('rabbitmq amqp', ret) |
705 | amulet.raise_status(amulet.FAIL, msg=message) |
706 | |
707 | - def test_nova_compute_relation(self): |
708 | + u.log.debug('OK') |
709 | + |
710 | + def test_202_nova_compute_relation(self): |
711 | """Verify the nova-compute to neutron-openvswitch relation data""" |
712 | + u.log.debug('Checking nova-compute:n-ovs neutron-plugin ' |
713 | + 'relation data...') |
714 | unit = self.compute_sentry |
715 | relation = ['neutron-plugin', 'neutron-openvswitch:neutron-plugin'] |
716 | expected = { |
717 | @@ -168,9 +207,13 @@ |
718 | message = u.relation_error('nova-compute neutron-plugin', ret) |
719 | amulet.raise_status(amulet.FAIL, msg=message) |
720 | |
721 | - def test_neutron_api_relation(self): |
722 | + u.log.debug('OK') |
723 | + |
724 | + def test_204_neutron_api_relation(self): |
725 | """Verify the neutron-api to neutron-openvswitch relation data""" |
726 | - unit = self.neutron_api_sentry |
727 | + u.log.debug('Checking neuron-api:neutron-ovs neutron-plugin-api ' |
728 | + 'relation data...') |
729 | + unit = self.n_api_sentry |
730 | relation = ['neutron-plugin-api', |
731 | 'neutron-openvswitch:neutron-plugin-api'] |
732 | expected = { |
733 | @@ -182,75 +225,117 @@ |
734 | message = u.relation_error('neutron-api neutron-plugin-api', ret) |
735 | amulet.raise_status(amulet.FAIL, msg=message) |
736 | |
737 | - def process_ret(self, ret=None, message=None): |
738 | + u.log.debug('OK') |
739 | + |
740 | + def _process_ret(self, ret=None, message=None): |
741 | if ret: |
742 | amulet.raise_status(amulet.FAIL, msg=message) |
743 | |
744 | - def check_ml2_setting_propagation(self, service, charm_key, |
745 | - config_file_key, vpair, |
746 | - section): |
747 | + def _check_ml2_setting_propagation(self, service, charm_key, |
748 | + config_file_key, vpair, |
749 | + section): |
750 | + |
751 | + # Needs love - test actions not clear in log |
752 | unit = self.compute_sentry |
753 | conf = "/etc/neutron/plugins/ml2/ml2_conf.ini" |
754 | for value in vpair: |
755 | self.d.configure(service, {charm_key: value}) |
756 | - time.sleep(60) |
757 | + self._wait_for_status() |
758 | + |
759 | ret = u.validate_config_data(unit, conf, section, |
760 | {config_file_key: value}) |
761 | msg = "Propagation error, expected %s=%s" % (config_file_key, |
762 | value) |
763 | - self.process_ret(ret=ret, message=msg) |
764 | - |
765 | - def test_l2pop_propagation(self): |
766 | + self._process_ret(ret=ret, message=msg) |
767 | + |
768 | + def test_300_neutron_api_default_config(self): |
769 | + u.log.warn('TEST NOT IMPLEMENTED') |
770 | + pass |
771 | + |
772 | + def test_302_l2pop_propagation(self): |
773 | """Verify that neutron-api l2pop setting propagates to neutron-ovs""" |
774 | - self.check_ml2_setting_propagation('neutron-api', |
775 | - 'l2-population', |
776 | - 'l2_population', |
777 | - ['False', 'True'], |
778 | - 'agent') |
779 | - |
780 | - def test_nettype_propagation(self): |
781 | + u.log.debug('Checking n-api l2pop setting propagation to n-ovs...') |
782 | + |
783 | + # Needs love - not idempotent |
784 | + self._check_ml2_setting_propagation('neutron-api', |
785 | + 'l2-population', |
786 | + 'l2_population', |
787 | + ['False', 'True'], |
788 | + 'agent') |
789 | + u.log.debug('OK') |
790 | + |
791 | + def test_303_nettype_propagation(self): |
792 | """Verify that neutron-api nettype setting propagates to neutron-ovs""" |
793 | - self.check_ml2_setting_propagation('neutron-api', |
794 | - 'overlay-network-type', |
795 | - 'tunnel_types', |
796 | - ['vxlan', 'gre'], |
797 | - 'agent') |
798 | - |
799 | - def test_secgroup_propagation_local_override(self): |
800 | + u.log.debug('Checking n-api nettype setting propagation to n-ovs...') |
801 | + |
802 | + # Needs love - not idempotent |
803 | + self._check_ml2_setting_propagation('neutron-api', |
804 | + 'overlay-network-type', |
805 | + 'tunnel_types', |
806 | + ['vxlan', 'gre'], |
807 | + 'agent') |
808 | + u.log.debug('OK') |
809 | + |
810 | + def test_304_secgroup_propagation_local_override(self): |
811 | """Verify disable-security-groups overrides what neutron-api says""" |
812 | + u.log.debug('Checking n-api disablee sec group override setting ' |
813 | + 'propagation to n-ovs...') |
814 | + |
815 | + # Needs love - not idempotent |
816 | unit = self.compute_sentry |
817 | conf = "/etc/neutron/plugins/ml2/ml2_conf.ini" |
818 | self.d.configure('neutron-api', {'neutron-security-groups': 'True'}) |
819 | self.d.configure('neutron-openvswitch', |
820 | {'disable-security-groups': 'True'}) |
821 | - time.sleep(30) |
822 | + |
823 | + self._wait_for_status() |
824 | + |
825 | ret = u.validate_config_data(unit, conf, 'securitygroup', |
826 | {'enable_security_group': 'False'}) |
827 | msg = "Propagation error, expected %s=%s" % ('enable_security_group', |
828 | 'False') |
829 | - self.process_ret(ret=ret, message=msg) |
830 | + self._process_ret(ret=ret, message=msg) |
831 | self.d.configure('neutron-openvswitch', |
832 | {'disable-security-groups': 'False'}) |
833 | self.d.configure('neutron-api', {'neutron-security-groups': 'True'}) |
834 | - time.sleep(30) |
835 | + |
836 | + self._wait_for_status() |
837 | ret = u.validate_config_data(unit, conf, 'securitygroup', |
838 | {'enable_security_group': 'True'}) |
839 | - |
840 | - def test_z_restart_on_config_change(self): |
841 | - """Verify that the specified services are restarted when the config |
842 | - is changed. |
843 | - |
844 | - Note(coreycb): The method name with the _z_ is a little odd |
845 | - but it forces the test to run last. It just makes things |
846 | - easier because restarting services requires re-authorization. |
847 | - """ |
848 | - conf = '/etc/neutron/neutron.conf' |
849 | - self.d.configure('neutron-openvswitch', {'use-syslog': 'True'}) |
850 | - if not u.service_restarted(self.compute_sentry, |
851 | - 'neutron-openvswitch-agent', conf, |
852 | - pgrep_full=True, sleep_time=60): |
853 | - self.d.configure('neutron-openvswitch', {'use-syslog': 'False'}) |
854 | - msg = ('service neutron-openvswitch-agent did not restart after ' |
855 | - 'config change') |
856 | - amulet.raise_status(amulet.FAIL, msg=msg) |
857 | - self.d.configure('neutron-openvswitch', {'use-syslog': 'False'}) |
858 | + u.log.debug('OK') |
859 | + |
860 | + def test_900_restart_on_config_change(self): |
861 | + """Verify that the specified services are restarted when the |
862 | + config is changed.""" |
863 | + |
864 | + sentry = self.n_ovs_sentry |
865 | + juju_service = 'neutron-openvswitch' |
866 | + |
867 | + # Expected default and alternate values |
868 | + set_default = {'debug': 'False'} |
869 | + set_alternate = {'debug': 'True'} |
870 | + |
871 | + # Services which are expected to restart upon config change, |
872 | + # and corresponding config files affected by the change |
873 | + conf_file = '/etc/neutron/neutron.conf' |
874 | + services = { |
875 | + 'neutron-openvswitch-agent': conf_file |
876 | + } |
877 | + |
878 | + # Make config change, check for svc restart, conf file mod time change |
879 | + u.log.debug('Making config change on {}...'.format(juju_service)) |
880 | + mtime = u.get_sentry_time(sentry) |
881 | + self.d.configure(juju_service, set_alternate) |
882 | + |
883 | + sleep_time = 60 |
884 | + for s, conf_file in services.iteritems(): |
885 | + u.log.debug("Checking that service restarted: {}".format(s)) |
886 | + if not u.validate_service_config_changed(sentry, mtime, s, |
887 | + conf_file, |
888 | + sleep_time=sleep_time): |
889 | + self.d.configure(juju_service, set_default) |
890 | + msg = "service {} didn't restart after config change".format(s) |
891 | + amulet.raise_status(amulet.FAIL, msg=msg) |
892 | + |
893 | + self.d.configure(juju_service, set_default) |
894 | + u.log.debug('OK') |
895 | |
896 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
897 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-21 22:49:35 +0000 |
898 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-26 21:25:29 +0000 |
899 | @@ -14,12 +14,18 @@ |
900 | # You should have received a copy of the GNU Lesser General Public License |
901 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
902 | |
903 | +import logging |
904 | +import re |
905 | +import sys |
906 | import six |
907 | from collections import OrderedDict |
908 | from charmhelpers.contrib.amulet.deployment import ( |
909 | AmuletDeployment |
910 | ) |
911 | |
912 | +DEBUG = logging.DEBUG |
913 | +ERROR = logging.ERROR |
914 | + |
915 | |
916 | class OpenStackAmuletDeployment(AmuletDeployment): |
917 | """OpenStack amulet deployment. |
918 | @@ -28,9 +34,12 @@ |
919 | that is specifically for use by OpenStack charms. |
920 | """ |
921 | |
922 | - def __init__(self, series=None, openstack=None, source=None, stable=True): |
923 | + def __init__(self, series=None, openstack=None, source=None, |
924 | + stable=True, log_level=DEBUG): |
925 | """Initialize the deployment environment.""" |
926 | super(OpenStackAmuletDeployment, self).__init__(series) |
927 | + self.log = self.get_logger(level=log_level) |
928 | + self.log.info('OpenStackAmuletDeployment: init') |
929 | self.openstack = openstack |
930 | self.source = source |
931 | self.stable = stable |
932 | @@ -38,6 +47,22 @@ |
933 | # out. |
934 | self.current_next = "trusty" |
935 | |
936 | + def get_logger(self, name="deployment-logger", level=logging.DEBUG): |
937 | + """Get a logger object that will log to stdout.""" |
938 | + log = logging |
939 | + logger = log.getLogger(name) |
940 | + fmt = log.Formatter("%(asctime)s %(funcName)s " |
941 | + "%(levelname)s: %(message)s") |
942 | + |
943 | + handler = log.StreamHandler(stream=sys.stdout) |
944 | + handler.setLevel(level) |
945 | + handler.setFormatter(fmt) |
946 | + |
947 | + logger.addHandler(handler) |
948 | + logger.setLevel(level) |
949 | + |
950 | + return logger |
951 | + |
952 | def _determine_branch_locations(self, other_services): |
953 | """Determine the branch locations for the other services. |
954 | |
955 | @@ -45,6 +70,8 @@ |
956 | stable or next (dev) branch, and based on this, use the corresonding |
957 | stable or next branches for the other_services.""" |
958 | |
959 | + self.log.info('OpenStackAmuletDeployment: determine branch locations') |
960 | + |
961 | # Charms outside the lp:~openstack-charmers namespace |
962 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
963 | |
964 | @@ -82,6 +109,8 @@ |
965 | |
966 | def _add_services(self, this_service, other_services): |
967 | """Add services to the deployment and set openstack-origin/source.""" |
968 | + self.log.info('OpenStackAmuletDeployment: adding services') |
969 | + |
970 | other_services = self._determine_branch_locations(other_services) |
971 | |
972 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
973 | @@ -111,9 +140,79 @@ |
974 | |
975 | def _configure_services(self, configs): |
976 | """Configure all of the services.""" |
977 | + self.log.info('OpenStackAmuletDeployment: configure services') |
978 | for service, config in six.iteritems(configs): |
979 | self.d.configure(service, config) |
980 | |
981 | + def _auto_wait_for_status(self, message=None, exclude_services=None, |
982 | + include_only=None, timeout=1800): |
983 | + """Wait for all units to have a specific extended status, except |
984 | + for any defined as excluded. Unless specified via message, any |
985 | + status containing any case of 'ready' will be considered a match. |
986 | + |
987 | + Examples of message usage: |
988 | + |
989 | + Wait for all unit status to CONTAIN any case of 'ready' or 'ok': |
990 | + message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) |
991 | + |
992 | + Wait for all units to reach this status (exact match): |
993 | + message = re.compile('^Unit is ready and clustered$') |
994 | + |
995 | + Wait for all units to reach any one of these (exact match): |
996 | + message = re.compile('Unit is ready|OK|Ready') |
997 | + |
998 | + Wait for at least one unit to reach this status (exact match): |
999 | + message = {'ready'} |
1000 | + |
1001 | + See Amulet's sentry.wait_for_messages() for message usage detail. |
1002 | + https://github.com/juju/amulet/blob/master/amulet/sentry.py |
1003 | + |
1004 | + :param message: Expected status match |
1005 | + :param exclude_services: List of juju service names to ignore, |
1006 | + not to be used in conjuction with include_only. |
1007 | + :param include_only: List of juju service names to exclusively check, |
1008 | + not to be used in conjuction with exclude_services. |
1009 | + :param timeout: Maximum time in seconds to wait for status match |
1010 | + :returns: None. Raises if timeout is hit. |
1011 | + """ |
1012 | + self.log.info('Waiting for extended status on units...') |
1013 | + |
1014 | + all_services = self.d.services.keys() |
1015 | + |
1016 | + if exclude_services and include_only: |
1017 | + raise ValueError('exclude_services can not be used ' |
1018 | + 'with include_only') |
1019 | + |
1020 | + if message: |
1021 | + if isinstance(message, re._pattern_type): |
1022 | + match = message.pattern |
1023 | + else: |
1024 | + match = message |
1025 | + |
1026 | + self.log.debug('Custom extended status wait match: ' |
1027 | + '{}'.format(match)) |
1028 | + else: |
1029 | + self.log.debug('Default extended status wait match: contains ' |
1030 | + 'READY (case-insensitive)') |
1031 | + message = re.compile('.*ready.*', re.IGNORECASE) |
1032 | + |
1033 | + if exclude_services: |
1034 | + self.log.debug('Excluding services from extended status match: ' |
1035 | + '{}'.format(exclude_services)) |
1036 | + else: |
1037 | + exclude_services = [] |
1038 | + |
1039 | + if include_only: |
1040 | + services = include_only |
1041 | + else: |
1042 | + services = list(set(all_services) - set(exclude_services)) |
1043 | + |
1044 | + self.log.debug('Waiting up to {}s for extended status on services: ' |
1045 | + '{}'.format(timeout, services)) |
1046 | + service_messages = {service: message for service in services} |
1047 | + self.d.sentry.wait_for_messages(service_messages, timeout=timeout) |
1048 | + self.log.info('OK') |
1049 | + |
1050 | def _get_openstack_release(self): |
1051 | """Get openstack release. |
1052 | |
1053 | |
1054 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' |
1055 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-29 20:59:00 +0000 |
1056 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-26 21:25:29 +0000 |
1057 | @@ -18,6 +18,7 @@ |
1058 | import json |
1059 | import logging |
1060 | import os |
1061 | +import re |
1062 | import six |
1063 | import time |
1064 | import urllib |
1065 | @@ -604,7 +605,22 @@ |
1066 | '{}'.format(sample_type, samples)) |
1067 | return None |
1068 | |
1069 | -# rabbitmq/amqp specific helpers: |
1070 | + # rabbitmq/amqp specific helpers: |
1071 | + |
1072 | + def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): |
1073 | + """Wait for rmq units extended status to show cluster readiness, |
1074 | + after an optional initial sleep period. Initial sleep is likely |
1075 | + necessary to be effective following a config change, as status |
1076 | + message may not instantly update to non-ready.""" |
1077 | + |
1078 | + if init_sleep: |
1079 | + time.sleep(init_sleep) |
1080 | + |
1081 | + message = re.compile('^Unit is ready and clustered$') |
1082 | + deployment._auto_wait_for_status(message=message, |
1083 | + timeout=timeout, |
1084 | + include_only=['rabbitmq-server']) |
1085 | + |
1086 | def add_rmq_test_user(self, sentry_units, |
1087 | username="testuser1", password="changeme"): |
1088 | """Add a test user via the first rmq juju unit, check connection as |
1089 | @@ -805,7 +821,10 @@ |
1090 | if port: |
1091 | config['ssl_port'] = port |
1092 | |
1093 | - deployment.configure('rabbitmq-server', config) |
1094 | + deployment.d.configure('rabbitmq-server', config) |
1095 | + |
1096 | + # Wait for unit status |
1097 | + self.rmq_wait_for_cluster(deployment) |
1098 | |
1099 | # Confirm |
1100 | tries = 0 |
1101 | @@ -832,7 +851,10 @@ |
1102 | |
1103 | # Disable RMQ SSL |
1104 | config = {'ssl': 'off'} |
1105 | - deployment.configure('rabbitmq-server', config) |
1106 | + deployment.d.configure('rabbitmq-server', config) |
1107 | + |
1108 | + # Wait for unit status |
1109 | + self.rmq_wait_for_cluster(deployment) |
1110 | |
1111 | # Confirm |
1112 | tries = 0 |
1113 | |
1114 | === added directory 'tests/setup' |
1115 | === renamed file 'tests/00-setup' => 'tests/setup/00-setup' |